図の右上のshowボタンを押すとRのコードが表示されます。

4.1 三つ以上の変数の可視化

4.1.1 レーダーチャートの印象

Rでレーダーチャートを描くパッケージはいくつかあるのだが、どれもイマイチ納得いかなかったのでポーラーチャートで勘弁して下さい。

中澤先生から教えていただいたfmsbパッケージを使う方法でキレイに描けました!

fmsbパッケージの日本語マニュアルはこちら

library(conflicted)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2
library(fmsb)

data1 <- data.frame(
  国語 = c(5, 1, 5, 3),
  理科 = c(5, 1, 5, 4),
  社会 = c(5, 1, 4, 3),
  体育 = c(5, 1, 4, 4),
  音楽 = c(5, 1, 5, 2),
  数学 = c(5, 1, 5, 2)
  )

data2 <- data.frame(
  数学 = c(5, 1, 5, 5),
  国語 = c(5, 1, 5, 1),
  理科 = c(5, 1, 1, 5),
  社会 = c(5, 1, 1, 1),
  体育 = c(5, 1, 1, 5),
  音楽 = c(5, 1, 5, 1)
  )

par(mfrow = c(2, 2), oma = c(0,0,0,0), mar = c(0,0,0,0))
# plot1
data1 |>
  select(数学, 国語, 理科, 社会, 体育, 音楽) |>
  radarchart(
    axistype = 4, seg = 4, pty = 32, pcol = c(4, 2),
    pfcol = c(adjustcolor("lightblue", 0.5), adjustcolor("pink", 0.5)),
    caxislabels=sprintf("%d", 1:5)
    ) 
text(-0.4, -0.3,  "Aさん", col="red")
text(-0.4, 0.6,"Bさん", col="blue")

# plot2
data1 |>
  select(音楽, 数学, 理科, 体育, 社会, 国語) |>
  radarchart(
    axistype = 4, seg = 4, pty = 32, pcol = c(4, 2),
    pfcol = c(adjustcolor("lightblue", 0.5), adjustcolor("pink", 0.5)),
    caxislabels=sprintf("%d", 1:5)
    ) 
text(-0.4, -0.3, "Aさん", col="red")
text(-0.4, 0.6, "Bさん", col="blue")

# plot3
data2 |>
  select(数学, 国語, 理科, 社会, 体育, 音楽) |>
  radarchart(
    axistype = 4, seg = 4, pty = 32, pcol = c(4, 2),
    pfcol = c(adjustcolor("lightblue", 0.5), adjustcolor("pink", 0.5)),
    caxislabels=sprintf("%d", 1:5)
    ) 
text(-0.4, -0.3, "Aさん", col="red")
text(-0.4, 0.6, "Bさん", col="blue")

# plot4
data2 |>
  select(音楽, 数学, 理科, 体育, 社会, 国語) |>
  radarchart(
    axistype = 4, seg = 4, pty = 32, pcol = c(4, 2),
    pfcol = c(adjustcolor("lightblue", 0.5), adjustcolor("pink", 0.5)),
    caxislabels=sprintf("%d", 1:5)
    ) 
text(-0.4, -0.3, "Aさん", col="red")
text(-0.4, 0.6, "Bさん", col="blue")

par(mfrow = c(1, 1))

4.1.2 各変数の特徴を概観して比較する

library(conflicted)
library(tidyverse)
library(patchwork)

v_names <- c(
    "ブドウの品種", "アルコール度数", "リンゴ酸", "ミネラル分", 
    "ミネラル分のアルカリ度", "マグネシウム", "全フェノール類", "フラバノイド",
    "非フラバノイドフェノール類", "プロアントシアニン", "色の強さ", "色相",
    "OD280/OD315値", "プロリン"
  )

df <- read_csv(
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",
  col_names = v_names,
  col_types = "f"
  ) |>
  mutate(across(where(is.numeric), \(x) scale(x))) |> #標準化
  rowid_to_column() |>
  pivot_longer(!c(ブドウの品種, rowid)) |>
  mutate(name = factor(name, levels = v_names))

df |>
  ggplot(aes(x = name, y = value, colour = ブドウの品種, group =rowid)) +
  geom_line(alpha = 0.5) +
  coord_flip() +
  labs(title = "ワインの特徴とブドウの品種", y = "相対スコア", x = "")

4.1.3 変数ごとに個別に可視化する方法

library(conflicted)
library(tidyverse)

v_names <- c(
    "ブドウの品種", "アルコール度数", "リンゴ酸", "ミネラル分", 
    "ミネラル分のアルカリ度", "マグネシウム", "全フェノール類", "フラバノイド",
    "非フラバノイドフェノール類", "プロアントシアニン", "色の強さ", "色相",
    "OD280/OD315値", "プロリン"
  )

df <- read_csv(
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",
  col_names = v_names,
  col_types = "f"
) |>
  mutate(across(where(is.numeric), \(x) scale(x))) |> #標準化
  rowid_to_column() |>
  pivot_longer(!c(ブドウの品種, rowid)) |>
  mutate(name = factor(name, levels = v_names))

p1 <- df |>
  ggplot(aes(x = name, y = value, group = ブドウの品種, fill = ブドウの品種)) +
  stat_summary(geom = "bar", fun = "mean", position = "dodge2") +
  stat_summary(geom = "errorbar", fun.data = "mean_se", position = "dodge2") +
  labs(y = "相対スコア", x = "") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        aspect.ratio = 1/2,
        legend.position = "none")

p2 <- df |>
  ggplot(aes(x = name, y = value, fill = ブドウの品種)) +
  geom_violin(position="dodge") +
  labs(y = "相対スコア", x = "") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        aspect.ratio = 1/2,
        legend.position = "none")

p1 / p2

4.1.4 ヒートマップ

library(conflicted)
library(tidyverse)

v_names <- c(
    "ブドウの品種", "アルコール度数", "リンゴ酸", "ミネラル分", 
    "ミネラル分のアルカリ度", "マグネシウム", "全フェノール類", "フラバノイド",
    "非フラバノイドフェノール類", "プロアントシアニン", "色の強さ", "色相",
    "OD280/OD315値", "プロリン"
  )

df <- read_csv(
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",
  col_names = v_names,
  col_types = "f"
  ) |>
  mutate(across(where(is.numeric), \(x) scale(x))) |> #標準化
  rowid_to_column() |>
  pivot_longer(!c(rowid, ブドウの品種)) |>
  mutate(name = factor(name, levels = rev(v_names)))

jet.colors <- colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))

df |>
  ggplot(aes(x = rowid, y = name, fill = value)) +
  geom_tile() +
  facet_wrap(vars(ブドウの品種), scales = "free_x") + 
  scale_fill_gradientn(colours = jet.colors(100)) +
  labs(x = "ワイン銘柄番号", y = "", title = "カラーコードで値を表現する")

4.1.5 各個体の行動時系列を可視化する

library(conflicted)
library(tidyverse)

df <- read_csv(
  "https://raw.githubusercontent.com/tkEzaki/data_visualization/main/4%E7%AB%A0/data/behavior_data.csv"
  ) |>
  mutate(Time = Time / 3600)  |> # Time列を秒から時間に変換
  pivot_longer(!Time) |>
  mutate(
    value = case_when(
      value == "Garbage" ~ "ゴミ捨て場",
      value == "Nest" ~ "寝室",
      value == "Other" ~ "一般の部屋",
      value == "Toilet" ~ "トイレ"
      ) |>
      factor(levels = c("ゴミ捨て場", "トイレ", "寝室", "一般の部屋"))
    ) 

df |>
  ggplot(aes(x =name , y = Time, fill = value)) +
  geom_tile() +
  scale_y_reverse() +
  scale_fill_brewer(palette = "Set1") +
  labs(title = "ヒートマップによる行動時系列の可視化", x = "個体", y = "時間[h]")

4.1.6 1個体の各行動の可視化

library(conflicted)
library(tidyverse)
library(patchwork)

# CSVデータを読み込む
df <- read_csv(
  "https://raw.githubusercontent.com/tkEzaki/data_visualization/main/4%E7%AB%A0/data/behavior_data.csv"
) |>
  mutate(Time = Time / 3600)  |> # Time列を秒から時間に変換
  pivot_longer(!Time) |>
  mutate(
    value = case_when(
      value == "Garbage" ~ "ゴミ捨て場",
      value == "Nest" ~ "寝室",
      value == "Other" ~ "一般の部屋",
      value == "Toilet" ~ "トイレ"
    ) |>
      factor(levels = rev(c("ゴミ捨て場", "トイレ", "寝室", "一般の部屋")))
  )

# 1個体(J)のデータ
df_j <- df |>
  dplyr::filter(name == "J") |> 
  dplyr::select(!name) |>
  mutate(action = "1")

p1 <- df_j |>
  ggplot(aes(x = value, y = Time, fill = value)) +
  geom_tile() +
  scale_y_reverse() +
  labs(x = "", y = "時間[h]") +
  scale_fill_brewer(palette = "Set1") +
  theme(
    legend.position="none",
    aspect.ratio = 4 / 1.5
    )

p2 <- df_j |>
  dplyr::filter(Time >= 13, Time <= 16) |>
  ggplot(aes(x = value, y = Time, fill = value)) +
  geom_tile() +
  scale_y_reverse() +
  labs(x = "", y = "時間[h]") +
  scale_fill_brewer(palette = "Set1") +
  theme(
    legend.position="none",
    aspect.ratio = 4 / 1.5
  )

p1 + p2

4.2.1 関係データのヒートマップによる可視化

library(conflicted)
library(tidyverse) 
library(patchwork)

# 共著データ
researchers <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
collaboration <- matrix(
  c(
    NA, 1, 1, 0, 0, 0, 0, 0, 0, 1,
    1, NA, 0, 1, 1, 1, 0, 0, 0, 1,
    1, 0, NA, 0, 0, 0, 1, 1, 1, 0,
    0, 1, 0, NA, 0, 0, 0, 0, 0, 0,
    0, 1, 0, 0, NA, 0, 0, 0, 0, 0,
    0, 1, 0, 0, 0, NA, 0, 0, 0, 0,
    0, 0, 1, 0, 0, 0, NA, 0, 0, 0,
    0, 0, 1, 0, 0, 0, 0, NA, 0, 0,
    0, 0, 1, 0, 0, 0, 0, 0, NA, 0,
    1, 1, 0, 0, 0, 0, 0, 0, 0, NA
    ),
  nrow=10,
  dimnames = list(researchers, researchers)
  )

p1 <- collaboration |>
  as.data.frame() |>
  rownames_to_column() |>
  pivot_longer(!rowname) |>
  mutate(
    value = factor(value),
    name = factor(name, levels = rev(LETTERS[1:10]))
    ) |>
  ggplot(aes(x = rowname, y = name, fill = value, label = value)) +
  geom_tile() +
  geom_text() +
  scale_fill_hue(name = "", labels = c("0" = "共著なし", "1" ="共著あり")) +
  labs(title = "共著関係の有無", x = "", y = "") +
  theme(aspect.ratio = 1)

# ここはもうちょっとスマートに書けないか?
set.seed(0)
v0 <- runif(n = (100 -10)/2, min = 0.0, max = 0.5) |> round(2)
v1 <- runif(n = (100 -10)/2, min = 0.5, max = 1.0) |> round(2)
collaboration_score <- collaboration
collaboration_score[upper.tri(collaboration_score)] <-
  if_else(collaboration[lower.tri(collaboration)]== 1, v1, v0) 
collaboration_score <- t(collaboration_score)
collaboration_score[upper.tri(collaboration_score)] <-
  if_else(collaboration[lower.tri(collaboration)]== 1, v1, v0)

jet.colors <- colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))

p2 <- collaboration_score |>
  as.data.frame() |>
  rownames_to_column() |>
  pivot_longer(!rowname) |>
  mutate(name = factor(name, levels = rev(LETTERS[1:10]))) |>
  ggplot(aes(x = rowname, y = name, fill = value, label = value)) +
  geom_tile() +
  geom_text(size = 2) +
  labs(title = "研究の興味の類似度", x = "", y = "") +
  theme(aspect.ratio = 1) +
  scale_fill_gradientn(colours = jet.colors(100))

p1 + p2

4.2.2 ネットワークによる関係データの可視化

ここはggraphで書き直すか?

library(conflicted)
library(tidyverse)
library(igraph)

# 共著データ
researchers <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
collaboration <- matrix(
  c(
    NA, 1, 1, 0, 0, 0, 0, 0, 0, 1,
    1, NA, 0, 1, 1, 1, 0, 0, 0, 1,
    1, 0, NA, 0, 0, 0, 1, 1, 1, 0,
    0, 1, 0, NA, 0, 0, 0, 0, 0, 0,
    0, 1, 0, 0, NA, 0, 0, 0, 0, 0,
    0, 1, 0, 0, 0, NA, 0, 0, 0, 0,
    0, 0, 1, 0, 0, 0, NA, 0, 0, 0,
    0, 0, 1, 0, 0, 0, 0, NA, 0, 0,
    0, 0, 1, 0, 0, 0, 0, 0, NA, 0,
    1, 1, 0, 0, 0, 0, 0, 0, 0, NA
  ),
  nrow=10,
  dimnames = list(researchers, researchers)
)

# 研究の興味の類似度
set.seed(0)
v0 <- runif(n = (100 -10)/2, min = 0.0, max = 0.5) |> round(2)
v1 <- runif(n = (100 -10)/2, min = 0.5, max = 1.0) |> round(2)
collaboration_score <- collaboration
collaboration_score[upper.tri(collaboration_score)] <-
  if_else(collaboration[lower.tri(collaboration)]== 1, v1, v0) 
collaboration_score <-
  t(collaboration_score)
collaboration_score[upper.tri(collaboration_score)] <-
  if_else(collaboration[lower.tri(collaboration)]== 1, v1, v0)

# 共著ネットワーク
diag(collaboration) <- 0
collabo_g <- collaboration |>
  graph_from_adjacency_matrix(mode = "undirected")

set.seed(0)

par(mfrow = c(2, 2), mar = c(0,0,0,0))

collabo_g |>
  plot(layout = layout_in_circle)

collabo_g |>
  plot()

# 類似度ネットワーク
diag(collaboration_score) <- 0
score_g <- collaboration_score |>
  graph_from_adjacency_matrix(mode = "undirected", weighted = TRUE)

jet.colors <- colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))
col_p <- round((E(score_g)$weight / max(E(score_g)$weight)) * 100, 0)

set.seed(0)

collabo_g |>
  plot(
    layout = layout_in_circle,
    edge.width = E(score_g)$weight * 10,
    edge.color = jet.colors(100)[col_p]
    )

score_g |>
  plot(
    edge.width = E(score_g)$weight * 10,
    edge.color = jet.colors(100)[col_p]
    )

par(mfrow = c(1, 1), mar = c(5.1, 4.1, 4.1, 2.1))

4.2.3 指導関係の可視化

4.2.3.1
library(conflicted)
library(tidyverse)

# 研究者リスト
researchers <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")

# 共著データ
collaboration <- matrix(
  c(
    NA, 1, 1, 0, 0, 0, 0, 0, 0, 1,
    0, NA, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, NA, 0, 0, 0, 1, 1, 1, 0,
    0, 0, 0, NA, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, NA, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, NA, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, NA, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, NA, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, NA, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, NA
    ),
  nrow = 10,
  dimnames = list(researchers, researchers)
  )

# 共著ネットワーク
collaboration |>
  as.data.frame() |>
  rownames_to_column() |>
  pivot_longer(!rowname) |>
  mutate(
    value = factor(value),
    name = factor(name, levels = rev(LETTERS[1:10]))
    ) |>
  ggplot(aes(x = rowname, y = name, fill = value, label = value)) +
  geom_tile() +
  geom_text() +
  scale_fill_hue(name = "", labels = c("0" = "指導関係なし", "1" ="指導関係あり")) +
  labs(title = "隣接行列表示", x = "指導された研究者", y = "指導した研究者") +
  theme(aspect.ratio = 1)

4.2.3.2
library(conflicted)
library(tidyverse)
library(igraph)

# 研究者リスト
researchers <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")

# 共著データ
collaboration <- matrix(
  c(
    NA, 1, 1, 0, 0, 0, 0, 0, 0, 1,
    0, NA, 0, 1, 1, 1, 0, 0, 0, 0,
    0, 0, NA, 0, 0, 0, 1, 1, 1, 0,
    0, 0, 0, NA, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, NA, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, NA, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, NA, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, NA, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, NA, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, NA
    ),
  nrow = 10,
  dimnames = list(researchers, researchers)
  )

diag(collaboration) <- 0

par(mar = c(0,0,0,0))

collaboration |>
  t() |>
  graph_from_adjacency_matrix(mode = "directed") |>
  plot(layout = layout_as_tree)

par(mar = c(5.1, 4.1, 4.1, 2.1))

4.2.4 様々なレイアウトによるネットワーク描画

circoレイアウトがigraphには無いようなのでMDSレイアウトで代用。

library(conflicted)
library(tidyverse)
library(igraph)

set.seed(0)
G_er <- erdos.renyi.game(30, 0.2)
G_ws <- watts.strogatz.game(1, 30, 5, 0.1)
G_ba <- barabasi.game(30, 1)

ErdosR <- as_adjacency_matrix(G_er, sparse = FALSE)
WattsStrogatz <- as_adjacency_matrix(G_ws, sparse = FALSE)
BarabasiAlbert <- as_adjacency_matrix(G_ba, sparse = FALSE)

net1 <- graph_from_adjacency_matrix(ErdosR, mode = "max")
net2 <- graph_from_adjacency_matrix(WattsStrogatz, mode = "max")
net3 <- graph_from_adjacency_matrix(BarabasiAlbert, mode = "max")

par(mfrow = c(3,3), mar = c(0,0,1,0))
plot(net1, layout = layout_in_circle,
     main = "Erdos-Reyni: Circular Layout", vertex.size = 9, vertex.label=NA, )
plot(net2, layout = layout_in_circle,
     main = "Watts-Strogatz: Circular Layout", vertex.size = 9, vertex.label=NA)
plot(net3, layout = layout_in_circle,
     main = "Barabasi-Albert: Circular Layout", vertex.size = 9, vertex.label=NA)

plot(net1, layout = layout_with_mds,
     main = "Erdos-Reyni: MDS Layout", vertex.size = 9, vertex.label=NA)
plot(net2, layout = layout_with_mds,
     main = "Watts-Strogatz: MDS Layout", vertex.size = 9, vertex.label=NA)
plot(net3, layout = layout_with_mds,
     main = "Barabasi-Albert: MDS Layout", vertex.size = 9, vertex.label=NA)

plot(net1, layout = layout_with_kk(net1),
     main = "Erdos-Reyni: KK Layout", vertex.size = 9, vertex.label=NA)
plot(net2, layout = layout_with_kk(net2),
     main = "Watts-Strogatz: KK Layout", vertex.size = 9, vertex.label=NA)
plot(net3, layout = layout_with_kk(net3),
     main = "Barabasi-Albert: KK Layout", vertex.size = 9, vertex.label=NA)

par(mfrow = c(1,1), mar = c(5.1, 4.1, 4.1, 2.1))

4.2.5 有向ネットワークの可視化

dotとcircoが無いのでtree, mdsで代用

library(conflicted)
library(tidyverse)
library(igraph)

# ランダム有向グラフを生成
set.seed(0)
G_dir_random <- sample_gnm(30, 81, directed = TRUE) 

# 階層構造を持つ有向グラフを生成
set.seed(0)
G_dir_hierarchy <- sample_tree(30, directed = TRUE)

my_plot <- function(data, layout){
  plot(
    data, layout = layout,
    vertex.label = NA,
    edge.arrow.size = 0.5
    )
}
par(mfrow = c(3,2), mar = c(0,0,0,0))
my_plot(G_dir_random, layout_as_tree)
my_plot(G_dir_hierarchy, layout_as_tree)
my_plot(G_dir_random, layout_with_mds)
my_plot(G_dir_hierarchy, layout_with_mds)
my_plot(G_dir_random, layout_with_kk)
my_plot(G_dir_hierarchy, layout_with_kk)

par(mfrow = c(1,1), mar = c(5.1, 4.1, 4.1, 2.1))

4.3.1 クラスターマップによるデータの可視化

library(conflicted)
library(tidyverse)
library(pheatmap)

df <- read_csv(
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data",
  col_names = c(
    "ブドウの品種", "アルコール度数", "リンゴ酸", "ミネラル分", 
    "ミネラル分のアルカリ度", "マグネシウム", "全フェノール類", "フラバノイド",
    "非フラバノイドフェノール類", "プロアントシアニン", "色の強さ", "色相",
    "OD280/OD315値", "プロリン"
  ),
  col_types = "f"
) |>
  mutate(across(where(is.numeric), \(x) scale(x))) #標準化

jet.colors <- colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))

pheatmap(
  df |>
    dplyr::select(!ブドウの品種) |>
    t(),
  color = jet.colors(50),
  clustering_method = "ward.D2"
  )

4.3.3 様々なクラスタリング手法

BIRCHだけ見つからないので省略。

ちなみにRではmlbenchパッケージにベンチマーク用の様々なデータセットと人工データ生成用の関数が用意されています。

library(conflicted)
library(tidyverse)
library(ClusterR)   # MiniBatch KMeans
library(apcluster)  # Affinity Propagation
library(meanShiftR) # MeanShift
library(skmeans)    # Spectral Clustering
library(cluster)    # Agglomerative Clustering
library(dbscan)     # DBSCAN, HDBSCAN, OPTICS,
library(mclust)     # Gaussian Mixture
library(patchwork)

# データ用意
my_read_csv <- function(file){
  read_csv(file, col_names = c("x", "y", "class")) |>
    mutate( #標準化しておく
      x = (x - mean(x))/sd(x),
      y = (y - mean(x))/sd(x),  
      class = factor(class + 1))
}

# 円形のクラスタ
noisy_circles <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/noisy_circles.csv"
  )

# 月型のクラスタ
noisy_moons <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/noisy_moons.csv"
  )     

# 正規分布に従うクラスタ
blobs <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/blobs.csv"
  )    

# 異方性のあるデータ
aniso <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/aniso.csv"
  )       

# 3つの正規分布に従うデータ
varied <- my_read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/varied.csv"
  )               

n <- 500
set.seed(0)
no_structure <- data.frame(x = runif(n), y = runif(n), class = factor("0"))# 構造のないデータ

# 各手法のラッパー関数を用意
# MiniBatch KMeans
my_MiniBatchKmeans <- function(data, cluster_num) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  fit <- MiniBatchKmeans(data_cleaned, clusters = cluster_num)
  pred <- predict(fit, data_cleaned)
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Affinity Propagation
my_apcluster <- function(data, cluster_num) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- apcluster(s = negDistMat(r=2), x = data_cleaned, p = -200, q = 0.9) |>
    cutree(cluster_num) |>
    labels(type = "enum")
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# MeanShift
my_meanShift <- function(data) {
  data_cleaned <- as.matrix(data[, 1:2])
  start <- Sys.time()
  pred <- meanShift(data_cleaned, data_cleaned)$assignment
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Spectral Clustering
my_skmeans <- function(data, cluster_num) {
  data_cleaned <- base::as.matrix(data[, 1:2])
  start <- Sys.time()
  pred <- skmeans(data_cleaned, k = cluster_num)$cluster
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Ward
my_hclust <- function(data, cluster_num) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- data_cleaned |>
    stats::dist() |>
    hclust(method = "ward.D2") |>
    cutree(k = cluster_num)
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Agglomerative Clustering
my_agnes <- function(data, cluster_num) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- agnes(data_cleaned) |> cutree(k = cluster_num)
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# DBSCAN
my_dbscan <- function(data) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- dbscan::dbscan(data_cleaned, eps = 0.3)$cluster
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# HDBSCAN
my_hdbscan <- function(data) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- dbscan::hdbscan(data_cleaned, minPts = 15)$cluster
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# OPTICS
my_optics <- function(data) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- dbscan::optics(data_cleaned, eps = 0.1, minPts = 7) |>
    extractXi(xi = 0.05) |>
    pluck("cluster")
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

# Gaussian Mixture
my_Mclust <- function(data) {
  data_cleaned <- data[, 1:2]
  start <- Sys.time()
  pred <- mclust::Mclust(data_cleaned, G = 1:3)$classification
  end <- Sys.time()
  diff <- end - start
  data$pred <- as.factor(pred)
  return(list(res = data, diff = diff))
}

### クラスタリング実行
dats <- list(noisy_circles, noisy_moons, varied, aniso, blobs, no_structure)
cnums <- list(2,2,3,3,3,3)

set.seed(0)
res_clustering <- list(
  res_MiniBatchKmeans = map2(dats, cnums, \(dats, cnums) my_MiniBatchKmeans(dats, cnums)),
  res_apcluster       = map2(dats, cnums, \(dats, cnums) my_apcluster(dats, cnums)),
  res_meanShift       = purrr::map(dats, \(dats) my_meanShift(dats)),
  res_skmeans         = map2(dats, cnums, \(dats, cnums) my_skmeans(dats, cnums)),
  res_hclust          = map2(dats, cnums, \(dats, cnums) my_hclust(dats, cnums)),
  res_agnes           = map2(dats, cnums, \(dats, cnums) my_agnes(dats, cnums)),
  res_dbscan          = purrr::map(dats, \(dats) my_dbscan(dats)),
  res_hdbscan         = purrr::map(dats, \(dats) my_hdbscan(dats)),
  res_optics          = purrr::map(dats, \(dats) my_optics(dats)),
  res_Mclust          = purrr::map(dats, \(dats) my_Mclust(dats))
) 

# 描画用ラッパー関数を用意
my_plot <- function(result) {
  result$res |>
    ggplot(aes(x = x, y = y, color = pred)) +
    geom_point(size = 1) + 
    labs(caption = paste0(round(result$diff,3),"s")) +
    theme(
      axis.ticks = element_blank(),  # tickの線を消す
      axis.text = element_blank(),   # tickの数字を消す
      axis.title = element_blank(),  # 軸のラベルを消す
      axis.line = element_blank(),   # 軸の線を消す
      legend.position="none",
      aspect.ratio = 1
      )
}

nums <- expand_grid(d = 1:6, m = 1:10)
res_plot <- map2(nums$m, nums$d, \(.x, .y) my_plot(res_clustering[[.x]][[.y]]))

# ここがダサい……もうちょっとどうにかならないか
res_plot[[1]] + res_plot[[2]] +res_plot[[3]] +res_plot[[4]] +res_plot[[5]] +res_plot[[6]] +
  res_plot[[7]] + res_plot[[8]] +res_plot[[9]] +res_plot[[10]] +res_plot[[11]] +res_plot[[12]] +
  res_plot[[13]] + res_plot[[14]] +res_plot[[15]] +res_plot[[16]] +res_plot[[17]] +res_plot[[18]] +
  res_plot[[19]] + res_plot[[20]] +res_plot[[21]] +res_plot[[22]] +res_plot[[23]] +res_plot[[24]] +
  res_plot[[25]] + res_plot[[26]] +res_plot[[27]] +res_plot[[28]] +res_plot[[29]] +res_plot[[30]] +
  res_plot[[31]] + res_plot[[32]] +res_plot[[33]] +res_plot[[34]] +res_plot[[35]] +res_plot[[36]] +
  res_plot[[37]] + res_plot[[38]] +res_plot[[39]] +res_plot[[40]] +res_plot[[41]] +res_plot[[42]] +
  res_plot[[43]] + res_plot[[44]] +res_plot[[45]] +res_plot[[46]] +res_plot[[47]] +res_plot[[48]] +
  res_plot[[49]] + res_plot[[50]] +res_plot[[51]] +res_plot[[52]] +res_plot[[53]] +res_plot[[54]] +
  res_plot[[55]] + res_plot[[56]] +res_plot[[57]] +res_plot[[58]] +res_plot[[59]] +res_plot[[60]] +
  plot_layout(ncol = 10)

4.3.4 多変数をペアプロットで見る

library(conflicted)
library(tidyverse)
library(GGally)

df <- read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/df_434.csv"
  )

df |>
  ggpairs()

4.3.5 主成分分析のイメージ

library(conflicted)
library(tidyverse)
library(skmeans)
library(GGally)
library(patchwork)

df <- read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/df_434.csv"
  )

# 主成分分析(標準化)
pca_result <- prcomp(df)#, scale. = TRUE)
pca_importance <- as.data.frame(summary(pca_result)$importance[2,]) |>
  rownames_to_column()
names(pca_importance) <- c("pc", "value")

# Spectral Clustering
clusters_pca <- skmeans(pca_result$x[, 1:2], 3)

p1 <- data.frame(
  pc1 = pca_result$x[, 1],
  pc2 = pca_result$x[, 2],
  class = factor(clusters_pca$cluster)
    ) |>
  ggplot(aes(x = pc1, y = pc2, color = class)) +
  geom_point() +
  labs(title = "二つの主成分で見る")+
  theme(legend.position="none", aspect.ratio = 1)

p2 <- pca_importance |>
  ggplot(aes(x = reorder(pc, desc(value)), y = value)) +
  geom_col()+
  labs(title = "各主成分のデータ悦明力", x = "", y = "")+
  theme(aspect.ratio = 1)

p1 + p2

4.3.6 画像データの次元削減

library(conflicted)
library(tidyverse)
library(MASS) # MDS(sammon)
library(Rtsne) # t-SNE
library(umap) # UMAP

df <- read_csv(
  "https://raw.githubusercontent.com/morimotoosamu/data_visualization/main/data/digits.csv"
  )

df_cleaned <- df |>
  dplyr::select(!target)

# 各手法で2次元に圧縮
# 主成分分析
X_pca <- prcomp(df_cleaned)$x[, 1:2]

# t-SNE
set.seed(42)
X_tsne <- Rtsne(df_cleaned, num_threads = 2)$Y

# MDS
X_mds <- df_cleaned |>
  dist() |>
  sammon(trace = FALSE) |>
  pluck("points")

# UMAP
set.seed(42)
X_umap <- umap(df_cleaned)$layout

# K-means
set.seed(42)
clusters_pca <- kmeans(X_pca, 10)$cluster  # PCA
set.seed(42)
clusters_mds <- kmeans(X_mds, 10)$cluster #MDS
set.seed(42)
clusters_tsne <- kmeans(X_tsne, 10)$cluster # t-SNE
set.seed(42)
clusters_umap <- kmeans(X_umap, 10)$cluster  # UMAP

# 結果をデータフレームにまとめる
df_base <- data.frame(
    x = c(X_pca[, 1], X_mds[, 1], X_tsne[, 1], X_umap[, 1]),
    y = c(X_pca[, 2], X_mds[, 2], X_tsne[, 2], X_umap[, 2])
    )

n <- 1797

dimred <- bind_rows(
  df_base |>
    mutate(
      label = rep(df$target, 4),
      method = c(rep("pca_label", n), rep("mds_label", n),rep("tsne_label", n),rep("umap_label", n))
      ),
  df_base |>
    mutate(
      label = c(clusters_pca, clusters_mds, clusters_tsne, clusters_umap),
      method = c(rep("pca_kmeans", n), rep("mds_kmeans", n),rep("tsne_kmeans", n),rep("umap_kmeans", n))
      )
) |>
  mutate(
    method = factor(
      method,
      levels = c("pca_kmeans", "pca_label", "mds_kmeans", "mds_label",
                 "tsne_kmeans",  "tsne_label", "umap_kmeans", "umap_label")),
    label = factor(label)
    )

# クラスタリング結果と正解ラベルの描画
dimred |>
  ggplot(aes(x = x, y = y, color = label)) + 
  geom_point() + 
  labs(title="様々な次元圧縮方法") +
  theme(
    axis.ticks = element_blank(),  # tickの線を消す
    axis.text = element_blank(),   # tickの数字を消す
    axis.title = element_blank(),  # 軸のラベルを消す
    axis.line = element_blank(),   # 軸の線を消す
    legend.position="none",
    aspect.ratio = 1
  ) +
  facet_wrap(vars(method), nrow = 2, scales = "free")

第4章はここまで。

LS0tCnRpdGxlOiAi56ysNOeroCDlpJrlpInmlbDjgpLjgajjgonjgYjjgovjg4fjg7zjgr/lj6/oppbljJYiCmF1dGhvcjogIk9zYW11LCBNT1JJTU9UTyIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0aGVtZTogdW5pdGVkICAgIAogICAgbWRfZXh0ZW5zaW9uczogIi1hc2NpaV9pZGVudGlmaWVycyIKICAgIHRvY19mbG9hdDogeWVzCiAgICBmaWdfd2lkdGg6IDcuNQogICAgZmlnX2hlaWdodDogNS42MjUKICAgIGRldjogcmFnZ19wbmcKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgrlm7Pjga7lj7PkuIrjga5gc2hvd2Djg5zjgr/jg7PjgpLmirzjgZnjgahS44Gu44Kz44O844OJ44GM6KGo56S644GV44KM44G+44GZ44CCCgojIyA0LjEg5LiJ44Gk5Lul5LiK44Gu5aSJ5pWw44Gu5Y+v6KaW5YyWCgojIyMgNC4xLjEg44Os44O844OA44O844OB44Oj44O844OI44Gu5Y2w6LGhCgp+flLjgafjg6zjg7zjg4Djg7zjg4Hjg6Pjg7zjg4jjgpLmj4/jgY/jg5Hjg4PjgrHjg7zjgrjjga/jgYTjgY/jgaTjgYvjgYLjgovjga7jgaDjgYzjgIHjganjgozjgoLjgqTjg57jgqTjg4HntI3lvpfjgYTjgYvjgarjgYvjgaPjgZ/jga7jgafjg53jg7zjg6njg7zjg4Hjg6Pjg7zjg4jjgafli5jlvIHjgZfjgabkuIvjgZXjgYTjgIJ+fgoKW+S4rea+pOWFiOeUn10oaHR0cHM6Ly90d2l0dGVyLmNvbS9NaW5hdG9OYWthemF3YSnjgYvjgonmlZnjgYjjgabjgYTjgZ/jgaDjgYTjgZ9bZm1zYuODkeODg+OCseODvOOCuOOCkuS9v+OBhuaWueazlV0oaHR0cHM6Ly9taW5hdG8uc2lwMjFjLm9yZy9pbTNyLzIwMjQwMTExLmh0bWwp44Gn44Kt44Os44Kk44Gr5o+P44GR44G+44GX44Gf77yBCgpmbXNi44OR44OD44Kx44O844K444Gu5pel5pys6Kqe44Oe44OL44Ol44Ki44Or44GvW+OBk+OBoeOCiV0oaHR0cHM6Ly9taW5hdG8uc2lwMjFjLm9yZy9tc2IvbWFuL2luZGV4Lmh0bWwp44CCCgpgYGB7cn0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShmbXNiKQoKZGF0YTEgPC0gZGF0YS5mcmFtZSgKICDlm73oqp4gPSBjKDUsIDEsIDUsIDMpLAogIOeQhuenkSA9IGMoNSwgMSwgNSwgNCksCiAg56S+5LyaID0gYyg1LCAxLCA0LCAzKSwKICDkvZPogrIgPSBjKDUsIDEsIDQsIDQpLAogIOmfs+alvSA9IGMoNSwgMSwgNSwgMiksCiAg5pWw5a2mID0gYyg1LCAxLCA1LCAyKQogICkKCmRhdGEyIDwtIGRhdGEuZnJhbWUoCiAg5pWw5a2mID0gYyg1LCAxLCA1LCA1KSwKICDlm73oqp4gPSBjKDUsIDEsIDUsIDEpLAogIOeQhuenkSA9IGMoNSwgMSwgMSwgNSksCiAg56S+5LyaID0gYyg1LCAxLCAxLCAxKSwKICDkvZPogrIgPSBjKDUsIDEsIDEsIDUpLAogIOmfs+alvSA9IGMoNSwgMSwgNSwgMSkKICApCgpwYXIobWZyb3cgPSBjKDIsIDIpLCBvbWEgPSBjKDAsMCwwLDApLCBtYXIgPSBjKDAsMCwwLDApKQojIHBsb3QxCmRhdGExIHw+CiAgc2VsZWN0KOaVsOWtpiwg5Zu96KqeLCDnkIbnp5EsIOekvuS8miwg5L2T6IKyLCDpn7Pmpb0pIHw+CiAgcmFkYXJjaGFydCgKICAgIGF4aXN0eXBlID0gNCwgc2VnID0gNCwgcHR5ID0gMzIsIHBjb2wgPSBjKDQsIDIpLAogICAgcGZjb2wgPSBjKGFkanVzdGNvbG9yKCJsaWdodGJsdWUiLCAwLjUpLCBhZGp1c3Rjb2xvcigicGluayIsIDAuNSkpLAogICAgY2F4aXNsYWJlbHM9c3ByaW50ZigiJWQiLCAxOjUpCiAgICApIAp0ZXh0KC0wLjQsIC0wLjMsICAiQeOBleOCkyIsIGNvbD0icmVkIikKdGV4dCgtMC40LCAwLjYsIkLjgZXjgpMiLCBjb2w9ImJsdWUiKQoKIyBwbG90MgpkYXRhMSB8PgogIHNlbGVjdCjpn7Pmpb0sIOaVsOWtpiwg55CG56eRLCDkvZPogrIsIOekvuS8miwg5Zu96KqeKSB8PgogIHJhZGFyY2hhcnQoCiAgICBheGlzdHlwZSA9IDQsIHNlZyA9IDQsIHB0eSA9IDMyLCBwY29sID0gYyg0LCAyKSwKICAgIHBmY29sID0gYyhhZGp1c3Rjb2xvcigibGlnaHRibHVlIiwgMC41KSwgYWRqdXN0Y29sb3IoInBpbmsiLCAwLjUpKSwKICAgIGNheGlzbGFiZWxzPXNwcmludGYoIiVkIiwgMTo1KQogICAgKSAKdGV4dCgtMC40LCAtMC4zLCAiQeOBleOCkyIsIGNvbD0icmVkIikKdGV4dCgtMC40LCAwLjYsICJC44GV44KTIiwgY29sPSJibHVlIikKCiMgcGxvdDMKZGF0YTIgfD4KICBzZWxlY3Qo5pWw5a2mLCDlm73oqp4sIOeQhuenkSwg56S+5LyaLCDkvZPogrIsIOmfs+alvSkgfD4KICByYWRhcmNoYXJ0KAogICAgYXhpc3R5cGUgPSA0LCBzZWcgPSA0LCBwdHkgPSAzMiwgcGNvbCA9IGMoNCwgMiksCiAgICBwZmNvbCA9IGMoYWRqdXN0Y29sb3IoImxpZ2h0Ymx1ZSIsIDAuNSksIGFkanVzdGNvbG9yKCJwaW5rIiwgMC41KSksCiAgICBjYXhpc2xhYmVscz1zcHJpbnRmKCIlZCIsIDE6NSkKICAgICkgCnRleHQoLTAuNCwgLTAuMywgIkHjgZXjgpMiLCBjb2w9InJlZCIpCnRleHQoLTAuNCwgMC42LCAiQuOBleOCkyIsIGNvbD0iYmx1ZSIpCgojIHBsb3Q0CmRhdGEyIHw+CiAgc2VsZWN0KOmfs+alvSwg5pWw5a2mLCDnkIbnp5EsIOS9k+iCsiwg56S+5LyaLCDlm73oqp4pIHw+CiAgcmFkYXJjaGFydCgKICAgIGF4aXN0eXBlID0gNCwgc2VnID0gNCwgcHR5ID0gMzIsIHBjb2wgPSBjKDQsIDIpLAogICAgcGZjb2wgPSBjKGFkanVzdGNvbG9yKCJsaWdodGJsdWUiLCAwLjUpLCBhZGp1c3Rjb2xvcigicGluayIsIDAuNSkpLAogICAgY2F4aXNsYWJlbHM9c3ByaW50ZigiJWQiLCAxOjUpCiAgICApIAp0ZXh0KC0wLjQsIC0wLjMsICJB44GV44KTIiwgY29sPSJyZWQiKQp0ZXh0KC0wLjQsIDAuNiwgIkLjgZXjgpMiLCBjb2w9ImJsdWUiKQpwYXIobWZyb3cgPSBjKDEsIDEpKQpgYGAKCgojIyMgNC4xLjIg5ZCE5aSJ5pWw44Gu54m55b6044KS5qaC6Kaz44GX44Gm5q+U6LyD44GZ44KLCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHBhdGNod29yaykKCnZfbmFtZXMgPC0gYygKICAgICLjg5bjg4njgqbjga7lk4HnqK4iLCAi44Ki44Or44Kz44O844Or5bqm5pWwIiwgIuODquODs+OCtOmFuCIsICLjg5/jg43jg6njg6vliIYiLCAKICAgICLjg5/jg43jg6njg6vliIbjga7jgqLjg6vjgqvjg6rluqYiLCAi44Oe44Kw44ON44K344Km44OgIiwgIuWFqOODleOCp+ODjuODvOODq+mhniIsICLjg5Xjg6njg5Djg47jgqTjg4kiLAogICAgIumdnuODleODqeODkOODjuOCpOODieODleOCp+ODjuODvOODq+mhniIsICLjg5fjg63jgqLjg7Pjg4jjgrfjgqLjg4vjg7MiLCAi6Imy44Gu5by344GVIiwgIuiJsuebuCIsCiAgICAiT0QyODAvT0QzMTXlgKQiLCAi44OX44Ot44Oq44OzIgogICkKCmRmIDwtIHJlYWRfY3N2KAogICJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvd2luZS93aW5lLmRhdGEiLAogIGNvbF9uYW1lcyA9IHZfbmFtZXMsCiAgY29sX3R5cGVzID0gImYiCiAgKSB8PgogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIFwoeCkgc2NhbGUoeCkpKSB8PiAj5qiZ5rqW5YyWCiAgcm93aWRfdG9fY29sdW1uKCkgfD4KICBwaXZvdF9sb25nZXIoIWMo44OW44OJ44Km44Gu5ZOB56iuLCByb3dpZCkpIHw+CiAgbXV0YXRlKG5hbWUgPSBmYWN0b3IobmFtZSwgbGV2ZWxzID0gdl9uYW1lcykpCgpkZiB8PgogIGdncGxvdChhZXMoeCA9IG5hbWUsIHkgPSB2YWx1ZSwgY29sb3VyID0g44OW44OJ44Km44Gu5ZOB56iuLCBncm91cCA9cm93aWQpKSArCiAgZ2VvbV9saW5lKGFscGhhID0gMC41KSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKHRpdGxlID0gIuODr+OCpOODs+OBrueJueW+tOOBqOODluODieOCpuOBruWTgeeoriIsIHkgPSAi55u45a++44K544Kz44KiIiwgeCA9ICIiKQpgYGAKCiMjIyA0LjEuMyDlpInmlbDjgZTjgajjgavlgIvliKXjgavlj6/oppbljJbjgZnjgovmlrnms5UKCmBgYHtyIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD03LjUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY29uZmxpY3RlZCkKbGlicmFyeSh0aWR5dmVyc2UpCgp2X25hbWVzIDwtIGMoCiAgICAi44OW44OJ44Km44Gu5ZOB56iuIiwgIuOCouODq+OCs+ODvOODq+W6puaVsCIsICLjg6rjg7PjgrTphbgiLCAi44Of44ON44Op44Or5YiGIiwgCiAgICAi44Of44ON44Op44Or5YiG44Gu44Ki44Or44Kr44Oq5bqmIiwgIuODnuOCsOODjeOCt+OCpuODoCIsICLlhajjg5Xjgqfjg47jg7zjg6vpoZ4iLCAi44OV44Op44OQ44OO44Kk44OJIiwKICAgICLpnZ7jg5Xjg6njg5Djg47jgqTjg4njg5Xjgqfjg47jg7zjg6vpoZ4iLCAi44OX44Ot44Ki44Oz44OI44K344Ki44OL44OzIiwgIuiJsuOBruW8t+OBlSIsICLoibLnm7giLAogICAgIk9EMjgwL09EMzE15YCkIiwgIuODl+ODreODquODsyIKICApCgpkZiA8LSByZWFkX2NzdigKICAiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzL3dpbmUvd2luZS5kYXRhIiwKICBjb2xfbmFtZXMgPSB2X25hbWVzLAogIGNvbF90eXBlcyA9ICJmIgopIHw+CiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgXCh4KSBzY2FsZSh4KSkpIHw+ICPmqJnmupbljJYKICByb3dpZF90b19jb2x1bW4oKSB8PgogIHBpdm90X2xvbmdlcighYyjjg5bjg4njgqbjga7lk4HnqK4sIHJvd2lkKSkgfD4KICBtdXRhdGUobmFtZSA9IGZhY3RvcihuYW1lLCBsZXZlbHMgPSB2X25hbWVzKSkKCnAxIDwtIGRmIHw+CiAgZ2dwbG90KGFlcyh4ID0gbmFtZSwgeSA9IHZhbHVlLCBncm91cCA9IOODluODieOCpuOBruWTgeeoriwgZmlsbCA9IOODluODieOCpuOBruWTgeeorikpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4gPSAibWVhbiIsIHBvc2l0aW9uID0gImRvZGdlMiIpICsKICBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gIm1lYW5fc2UiLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSArCiAgbGFicyh5ID0gIuebuOWvvuOCueOCs+OCoiIsIHggPSAiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgYXNwZWN0LnJhdGlvID0gMS8yLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnAyIDwtIGRmIHw+CiAgZ2dwbG90KGFlcyh4ID0gbmFtZSwgeSA9IHZhbHVlLCBmaWxsID0g44OW44OJ44Km44Gu5ZOB56iuKSkgKwogIGdlb21fdmlvbGluKHBvc2l0aW9uPSJkb2RnZSIpICsKICBsYWJzKHkgPSAi55u45a++44K544Kz44KiIiwgeCA9ICIiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICBhc3BlY3QucmF0aW8gPSAxLzIsCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKcDEgLyBwMgpgYGAKCiMjIyA0LjEuNCDjg5Ljg7zjg4jjg57jg4Pjg5cKCmBgYHtyICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQoKdl9uYW1lcyA8LSBjKAogICAgIuODluODieOCpuOBruWTgeeoriIsICLjgqLjg6vjgrPjg7zjg6vluqbmlbAiLCAi44Oq44Oz44K06YW4IiwgIuODn+ODjeODqeODq+WIhiIsIAogICAgIuODn+ODjeODqeODq+WIhuOBruOCouODq+OCq+ODquW6piIsICLjg57jgrDjg43jgrfjgqbjg6AiLCAi5YWo44OV44Kn44OO44O844Or6aGeIiwgIuODleODqeODkOODjuOCpOODiSIsCiAgICAi6Z2e44OV44Op44OQ44OO44Kk44OJ44OV44Kn44OO44O844Or6aGeIiwgIuODl+ODreOCouODs+ODiOOCt+OCouODi+ODsyIsICLoibLjga7lvLfjgZUiLCAi6Imy55u4IiwKICAgICJPRDI4MC9PRDMxNeWApCIsICLjg5fjg63jg6rjg7MiCiAgKQoKZGYgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy93aW5lL3dpbmUuZGF0YSIsCiAgY29sX25hbWVzID0gdl9uYW1lcywKICBjb2xfdHlwZXMgPSAiZiIKICApIHw+CiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgXCh4KSBzY2FsZSh4KSkpIHw+ICPmqJnmupbljJYKICByb3dpZF90b19jb2x1bW4oKSB8PgogIHBpdm90X2xvbmdlcighYyhyb3dpZCwg44OW44OJ44Km44Gu5ZOB56iuKSkgfD4KICBtdXRhdGUobmFtZSA9IGZhY3RvcihuYW1lLCBsZXZlbHMgPSByZXYodl9uYW1lcykpKQoKamV0LmNvbG9ycyA8LSBjb2xvclJhbXBQYWxldHRlKGMoIiMwMDAwN0YiLCAiYmx1ZSIsICIjMDA3RkZGIiwgImN5YW4iLCAiIzdGRkY3RiIsICJ5ZWxsb3ciLCAiI0ZGN0YwMCIsICJyZWQiLCAiIzdGMDAwMCIpKQoKZGYgfD4KICBnZ3Bsb3QoYWVzKHggPSByb3dpZCwgeSA9IG5hbWUsIGZpbGwgPSB2YWx1ZSkpICsKICBnZW9tX3RpbGUoKSArCiAgZmFjZXRfd3JhcCh2YXJzKOODluODieOCpuOBruWTgeeoriksIHNjYWxlcyA9ICJmcmVlX3giKSArIAogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBqZXQuY29sb3JzKDEwMCkpICsKICBsYWJzKHggPSAi44Ov44Kk44Oz6YqY5p+E55Wq5Y+3IiwgeSA9ICIiLCB0aXRsZSA9ICLjgqvjg6njg7zjgrPjg7zjg4njgaflgKTjgpLooajnj77jgZnjgosiKQoKYGBgCgojIyMgNC4xLjUg5ZCE5YCL5L2T44Gu6KGM5YuV5pmC57O75YiX44KS5Y+v6KaW5YyW44GZ44KLCgpgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCmRmIDwtIHJlYWRfY3N2KAogICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdGtFemFraS9kYXRhX3Zpc3VhbGl6YXRpb24vbWFpbi80JUU3JUFCJUEwL2RhdGEvYmVoYXZpb3JfZGF0YS5jc3YiCiAgKSB8PgogIG11dGF0ZShUaW1lID0gVGltZSAvIDM2MDApICB8PiAjIFRpbWXliJfjgpLnp5LjgYvjgonmmYLplpPjgavlpInmj5sKICBwaXZvdF9sb25nZXIoIVRpbWUpIHw+CiAgbXV0YXRlKAogICAgdmFsdWUgPSBjYXNlX3doZW4oCiAgICAgIHZhbHVlID09ICJHYXJiYWdlIiB+ICLjgrTjg5/mjajjgabloLQiLAogICAgICB2YWx1ZSA9PSAiTmVzdCIgfiAi5a+d5a6kIiwKICAgICAgdmFsdWUgPT0gIk90aGVyIiB+ICLkuIDoiKzjga7pg6jlsYsiLAogICAgICB2YWx1ZSA9PSAiVG9pbGV0IiB+ICLjg4jjgqTjg6wiCiAgICAgICkgfD4KICAgICAgZmFjdG9yKGxldmVscyA9IGMoIuOCtOODn+aNqOOBpuWgtCIsICLjg4jjgqTjg6wiLCAi5a+d5a6kIiwgIuS4gOiIrOOBrumDqOWxiyIpKQogICAgKSAKCmRmIHw+CiAgZ2dwbG90KGFlcyh4ID1uYW1lICwgeSA9IFRpbWUsIGZpbGwgPSB2YWx1ZSkpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfeV9yZXZlcnNlKCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBsYWJzKHRpdGxlID0gIuODkuODvOODiOODnuODg+ODl+OBq+OCiOOCi+ihjOWLleaZguezu+WIl+OBruWPr+imluWMliIsIHggPSAi5YCL5L2TIiwgeSA9ICLmmYLplpNbaF0iKQpgYGAKCiMjIyA0LjEuNiAx5YCL5L2T44Gu5ZCE6KGM5YuV44Gu5Y+v6KaW5YyWCgpgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwYXRjaHdvcmspCgojIENTVuODh+ODvOOCv+OCkuiqreOBv+i+vOOCgApkZiA8LSByZWFkX2NzdigKICAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3RrRXpha2kvZGF0YV92aXN1YWxpemF0aW9uL21haW4vNCVFNyVBQiVBMC9kYXRhL2JlaGF2aW9yX2RhdGEuY3N2IgopIHw+CiAgbXV0YXRlKFRpbWUgPSBUaW1lIC8gMzYwMCkgIHw+ICMgVGltZeWIl+OCkuenkuOBi+OCieaZgumWk+OBq+WkieaPmwogIHBpdm90X2xvbmdlcighVGltZSkgfD4KICBtdXRhdGUoCiAgICB2YWx1ZSA9IGNhc2Vfd2hlbigKICAgICAgdmFsdWUgPT0gIkdhcmJhZ2UiIH4gIuOCtOODn+aNqOOBpuWgtCIsCiAgICAgIHZhbHVlID09ICJOZXN0IiB+ICLlr53lrqQiLAogICAgICB2YWx1ZSA9PSAiT3RoZXIiIH4gIuS4gOiIrOOBrumDqOWxiyIsCiAgICAgIHZhbHVlID09ICJUb2lsZXQiIH4gIuODiOOCpOODrCIKICAgICkgfD4KICAgICAgZmFjdG9yKGxldmVscyA9IHJldihjKCLjgrTjg5/mjajjgabloLQiLCAi44OI44Kk44OsIiwgIuWvneWupCIsICLkuIDoiKzjga7pg6jlsYsiKSkpCiAgKQoKIyAx5YCL5L2TKEop44Gu44OH44O844K/CmRmX2ogPC0gZGYgfD4KICBkcGx5cjo6ZmlsdGVyKG5hbWUgPT0gIkoiKSB8PiAKICBkcGx5cjo6c2VsZWN0KCFuYW1lKSB8PgogIG11dGF0ZShhY3Rpb24gPSAiMSIpCgpwMSA8LSBkZl9qIHw+CiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUsIHkgPSBUaW1lLCBmaWxsID0gdmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX3lfcmV2ZXJzZSgpICsKICBsYWJzKHggPSAiIiwgeSA9ICLmmYLplpNbaF0iKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgIGFzcGVjdC5yYXRpbyA9IDQgLyAxLjUKICAgICkKCnAyIDwtIGRmX2ogfD4KICBkcGx5cjo6ZmlsdGVyKFRpbWUgPj0gMTMsIFRpbWUgPD0gMTYpIHw+CiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUsIHkgPSBUaW1lLCBmaWxsID0gdmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX3lfcmV2ZXJzZSgpICsKICBsYWJzKHggPSAiIiwgeSA9ICLmmYLplpNbaF0iKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgIGFzcGVjdC5yYXRpbyA9IDQgLyAxLjUKICApCgpwMSArIHAyCmBgYAoKIyMjIDQuMi4xIOmWouS/guODh+ODvOOCv+OBruODkuODvOODiOODnuODg+ODl+OBq+OCiOOCi+WPr+imluWMlgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkgCmxpYnJhcnkocGF0Y2h3b3JrKQoKIyDlhbHokZfjg4fjg7zjgr8KcmVzZWFyY2hlcnMgPC0gYygiQSIsICJCIiwgIkMiLCAiRCIsICJFIiwgIkYiLCAiRyIsICJIIiwgIkkiLCAiSiIpCmNvbGxhYm9yYXRpb24gPC0gbWF0cml4KAogIGMoCiAgICBOQSwgMSwgMSwgMCwgMCwgMCwgMCwgMCwgMCwgMSwKICAgIDEsIE5BLCAwLCAxLCAxLCAxLCAwLCAwLCAwLCAxLAogICAgMSwgMCwgTkEsIDAsIDAsIDAsIDEsIDEsIDEsIDAsCiAgICAwLCAxLCAwLCBOQSwgMCwgMCwgMCwgMCwgMCwgMCwKICAgIDAsIDEsIDAsIDAsIE5BLCAwLCAwLCAwLCAwLCAwLAogICAgMCwgMSwgMCwgMCwgMCwgTkEsIDAsIDAsIDAsIDAsCiAgICAwLCAwLCAxLCAwLCAwLCAwLCBOQSwgMCwgMCwgMCwKICAgIDAsIDAsIDEsIDAsIDAsIDAsIDAsIE5BLCAwLCAwLAogICAgMCwgMCwgMSwgMCwgMCwgMCwgMCwgMCwgTkEsIDAsCiAgICAxLCAxLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCBOQQogICAgKSwKICBucm93PTEwLAogIGRpbW5hbWVzID0gbGlzdChyZXNlYXJjaGVycywgcmVzZWFyY2hlcnMpCiAgKQoKcDEgPC0gY29sbGFib3JhdGlvbiB8PgogIGFzLmRhdGEuZnJhbWUoKSB8PgogIHJvd25hbWVzX3RvX2NvbHVtbigpIHw+CiAgcGl2b3RfbG9uZ2VyKCFyb3duYW1lKSB8PgogIG11dGF0ZSgKICAgIHZhbHVlID0gZmFjdG9yKHZhbHVlKSwKICAgIG5hbWUgPSBmYWN0b3IobmFtZSwgbGV2ZWxzID0gcmV2KExFVFRFUlNbMToxMF0pKQogICAgKSB8PgogIGdncGxvdChhZXMoeCA9IHJvd25hbWUsIHkgPSBuYW1lLCBmaWxsID0gdmFsdWUsIGxhYmVsID0gdmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIGdlb21fdGV4dCgpICsKICBzY2FsZV9maWxsX2h1ZShuYW1lID0gIiIsIGxhYmVscyA9IGMoIjAiID0gIuWFseiRl+OBquOBlyIsICIxIiA9IuWFseiRl+OBguOCiiIpKSArCiAgbGFicyh0aXRsZSA9ICLlhbHokZfplqLkv4Ljga7mnInnhKEiLCB4ID0gIiIsIHkgPSAiIikgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpCgojIOOBk+OBk+OBr+OCguOBhuOBoeOCh+OBo+OBqOOCueODnuODvOODiOOBq+abuOOBkeOBquOBhOOBi++8nwpzZXQuc2VlZCgwKQp2MCA8LSBydW5pZihuID0gKDEwMCAtMTApLzIsIG1pbiA9IDAuMCwgbWF4ID0gMC41KSB8PiByb3VuZCgyKQp2MSA8LSBydW5pZihuID0gKDEwMCAtMTApLzIsIG1pbiA9IDAuNSwgbWF4ID0gMS4wKSB8PiByb3VuZCgyKQpjb2xsYWJvcmF0aW9uX3Njb3JlIDwtIGNvbGxhYm9yYXRpb24KY29sbGFib3JhdGlvbl9zY29yZVt1cHBlci50cmkoY29sbGFib3JhdGlvbl9zY29yZSldIDwtCiAgaWZfZWxzZShjb2xsYWJvcmF0aW9uW2xvd2VyLnRyaShjb2xsYWJvcmF0aW9uKV09PSAxLCB2MSwgdjApIApjb2xsYWJvcmF0aW9uX3Njb3JlIDwtIHQoY29sbGFib3JhdGlvbl9zY29yZSkKY29sbGFib3JhdGlvbl9zY29yZVt1cHBlci50cmkoY29sbGFib3JhdGlvbl9zY29yZSldIDwtCiAgaWZfZWxzZShjb2xsYWJvcmF0aW9uW2xvd2VyLnRyaShjb2xsYWJvcmF0aW9uKV09PSAxLCB2MSwgdjApCgpqZXQuY29sb3JzIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygiIzAwMDA3RiIsICJibHVlIiwgIiMwMDdGRkYiLCAiY3lhbiIsICIjN0ZGRjdGIiwgInllbGxvdyIsICIjRkY3RjAwIiwgInJlZCIsICIjN0YwMDAwIikpCgpwMiA8LSBjb2xsYWJvcmF0aW9uX3Njb3JlIHw+CiAgYXMuZGF0YS5mcmFtZSgpIHw+CiAgcm93bmFtZXNfdG9fY29sdW1uKCkgfD4KICBwaXZvdF9sb25nZXIoIXJvd25hbWUpIHw+CiAgbXV0YXRlKG5hbWUgPSBmYWN0b3IobmFtZSwgbGV2ZWxzID0gcmV2KExFVFRFUlNbMToxMF0pKSkgfD4KICBnZ3Bsb3QoYWVzKHggPSByb3duYW1lLCB5ID0gbmFtZSwgZmlsbCA9IHZhbHVlLCBsYWJlbCA9IHZhbHVlKSkgKwogIGdlb21fdGlsZSgpICsKICBnZW9tX3RleHQoc2l6ZSA9IDIpICsKICBsYWJzKHRpdGxlID0gIueglOeptuOBruiIiOWRs+OBrumhnuS8vOW6piIsIHggPSAiIiwgeSA9ICIiKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBqZXQuY29sb3JzKDEwMCkpCgpwMSArIHAyCmBgYAoKIyMjIDQuMi4yIOODjeODg+ODiOODr+ODvOOCr+OBq+OCiOOCi+mWouS/guODh+ODvOOCv+OBruWPr+imluWMlgoK44GT44GT44GvZ2dyYXBo44Gn5pu444GN55u044GZ44GL77yfCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGlncmFwaCkKCiMg5YWx6JGX44OH44O844K/CnJlc2VhcmNoZXJzIDwtIGMoIkEiLCAiQiIsICJDIiwgIkQiLCAiRSIsICJGIiwgIkciLCAiSCIsICJJIiwgIkoiKQpjb2xsYWJvcmF0aW9uIDwtIG1hdHJpeCgKICBjKAogICAgTkEsIDEsIDEsIDAsIDAsIDAsIDAsIDAsIDAsIDEsCiAgICAxLCBOQSwgMCwgMSwgMSwgMSwgMCwgMCwgMCwgMSwKICAgIDEsIDAsIE5BLCAwLCAwLCAwLCAxLCAxLCAxLCAwLAogICAgMCwgMSwgMCwgTkEsIDAsIDAsIDAsIDAsIDAsIDAsCiAgICAwLCAxLCAwLCAwLCBOQSwgMCwgMCwgMCwgMCwgMCwKICAgIDAsIDEsIDAsIDAsIDAsIE5BLCAwLCAwLCAwLCAwLAogICAgMCwgMCwgMSwgMCwgMCwgMCwgTkEsIDAsIDAsIDAsCiAgICAwLCAwLCAxLCAwLCAwLCAwLCAwLCBOQSwgMCwgMCwKICAgIDAsIDAsIDEsIDAsIDAsIDAsIDAsIDAsIE5BLCAwLAogICAgMSwgMSwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgTkEKICApLAogIG5yb3c9MTAsCiAgZGltbmFtZXMgPSBsaXN0KHJlc2VhcmNoZXJzLCByZXNlYXJjaGVycykKKQoKIyDnoJTnqbbjga7oiIjlkbPjga7poZ7kvLzluqYKc2V0LnNlZWQoMCkKdjAgPC0gcnVuaWYobiA9ICgxMDAgLTEwKS8yLCBtaW4gPSAwLjAsIG1heCA9IDAuNSkgfD4gcm91bmQoMikKdjEgPC0gcnVuaWYobiA9ICgxMDAgLTEwKS8yLCBtaW4gPSAwLjUsIG1heCA9IDEuMCkgfD4gcm91bmQoMikKY29sbGFib3JhdGlvbl9zY29yZSA8LSBjb2xsYWJvcmF0aW9uCmNvbGxhYm9yYXRpb25fc2NvcmVbdXBwZXIudHJpKGNvbGxhYm9yYXRpb25fc2NvcmUpXSA8LQogIGlmX2Vsc2UoY29sbGFib3JhdGlvbltsb3dlci50cmkoY29sbGFib3JhdGlvbildPT0gMSwgdjEsIHYwKSAKY29sbGFib3JhdGlvbl9zY29yZSA8LQogIHQoY29sbGFib3JhdGlvbl9zY29yZSkKY29sbGFib3JhdGlvbl9zY29yZVt1cHBlci50cmkoY29sbGFib3JhdGlvbl9zY29yZSldIDwtCiAgaWZfZWxzZShjb2xsYWJvcmF0aW9uW2xvd2VyLnRyaShjb2xsYWJvcmF0aW9uKV09PSAxLCB2MSwgdjApCgojIOWFseiRl+ODjeODg+ODiOODr+ODvOOCrwpkaWFnKGNvbGxhYm9yYXRpb24pIDwtIDAKY29sbGFib19nIDwtIGNvbGxhYm9yYXRpb24gfD4KICBncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgobW9kZSA9ICJ1bmRpcmVjdGVkIikKCnNldC5zZWVkKDApCgpwYXIobWZyb3cgPSBjKDIsIDIpLCBtYXIgPSBjKDAsMCwwLDApKQoKY29sbGFib19nIHw+CiAgcGxvdChsYXlvdXQgPSBsYXlvdXRfaW5fY2lyY2xlKQoKY29sbGFib19nIHw+CiAgcGxvdCgpCgojIOmhnuS8vOW6puODjeODg+ODiOODr+ODvOOCrwpkaWFnKGNvbGxhYm9yYXRpb25fc2NvcmUpIDwtIDAKc2NvcmVfZyA8LSBjb2xsYWJvcmF0aW9uX3Njb3JlIHw+CiAgZ3JhcGhfZnJvbV9hZGphY2VuY3lfbWF0cml4KG1vZGUgPSAidW5kaXJlY3RlZCIsIHdlaWdodGVkID0gVFJVRSkKCmpldC5jb2xvcnMgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCIjMDAwMDdGIiwgImJsdWUiLCAiIzAwN0ZGRiIsICJjeWFuIiwgIiM3RkZGN0YiLCAieWVsbG93IiwgIiNGRjdGMDAiLCAicmVkIiwgIiM3RjAwMDAiKSkKY29sX3AgPC0gcm91bmQoKEUoc2NvcmVfZykkd2VpZ2h0IC8gbWF4KEUoc2NvcmVfZykkd2VpZ2h0KSkgKiAxMDAsIDApCgpzZXQuc2VlZCgwKQoKY29sbGFib19nIHw+CiAgcGxvdCgKICAgIGxheW91dCA9IGxheW91dF9pbl9jaXJjbGUsCiAgICBlZGdlLndpZHRoID0gRShzY29yZV9nKSR3ZWlnaHQgKiAxMCwKICAgIGVkZ2UuY29sb3IgPSBqZXQuY29sb3JzKDEwMClbY29sX3BdCiAgICApCgpzY29yZV9nIHw+CiAgcGxvdCgKICAgIGVkZ2Uud2lkdGggPSBFKHNjb3JlX2cpJHdlaWdodCAqIDEwLAogICAgZWRnZS5jb2xvciA9IGpldC5jb2xvcnMoMTAwKVtjb2xfcF0KICAgICkKCnBhcihtZnJvdyA9IGMoMSwgMSksIG1hciA9IGMoNS4xLCA0LjEsIDQuMSwgMi4xKSkKYGBgCgojIyMjIDQuMi4z44CA5oyH5bCO6Zai5L+C44Gu5Y+v6KaW5YyWCgojIyMjIyA0LjIuMy4xCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQoKIyDnoJTnqbbogIXjg6rjgrnjg4gKcmVzZWFyY2hlcnMgPC0gYygiQSIsICJCIiwgIkMiLCAiRCIsICJFIiwgIkYiLCAiRyIsICJIIiwgIkkiLCAiSiIpCgojIOWFseiRl+ODh+ODvOOCvwpjb2xsYWJvcmF0aW9uIDwtIG1hdHJpeCgKICBjKAogICAgTkEsIDEsIDEsIDAsIDAsIDAsIDAsIDAsIDAsIDEsCiAgICAwLCBOQSwgMCwgMSwgMSwgMSwgMCwgMCwgMCwgMCwKICAgIDAsIDAsIE5BLCAwLCAwLCAwLCAxLCAxLCAxLCAwLAogICAgMCwgMCwgMCwgTkEsIDAsIDAsIDAsIDAsIDAsIDAsCiAgICAwLCAwLCAwLCAwLCBOQSwgMCwgMCwgMCwgMCwgMCwKICAgIDAsIDAsIDAsIDAsIDAsIE5BLCAwLCAwLCAwLCAwLAogICAgMCwgMCwgMCwgMCwgMCwgMCwgTkEsIDAsIDAsIDAsCiAgICAwLCAwLCAwLCAwLCAwLCAwLCAwLCBOQSwgMCwgMCwKICAgIDAsIDAsIDAsIDAsIDAsIDAsIDAsIDAsIE5BLCAwLAogICAgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgTkEKICAgICksCiAgbnJvdyA9IDEwLAogIGRpbW5hbWVzID0gbGlzdChyZXNlYXJjaGVycywgcmVzZWFyY2hlcnMpCiAgKQoKIyDlhbHokZfjg43jg4Pjg4jjg6/jg7zjgq8KY29sbGFib3JhdGlvbiB8PgogIGFzLmRhdGEuZnJhbWUoKSB8PgogIHJvd25hbWVzX3RvX2NvbHVtbigpIHw+CiAgcGl2b3RfbG9uZ2VyKCFyb3duYW1lKSB8PgogIG11dGF0ZSgKICAgIHZhbHVlID0gZmFjdG9yKHZhbHVlKSwKICAgIG5hbWUgPSBmYWN0b3IobmFtZSwgbGV2ZWxzID0gcmV2KExFVFRFUlNbMToxMF0pKQogICAgKSB8PgogIGdncGxvdChhZXMoeCA9IHJvd25hbWUsIHkgPSBuYW1lLCBmaWxsID0gdmFsdWUsIGxhYmVsID0gdmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIGdlb21fdGV4dCgpICsKICBzY2FsZV9maWxsX2h1ZShuYW1lID0gIiIsIGxhYmVscyA9IGMoIjAiID0gIuaMh+WwjumWouS/guOBquOBlyIsICIxIiA9IuaMh+WwjumWouS/guOBguOCiiIpKSArCiAgbGFicyh0aXRsZSA9ICLpmqPmjqXooYzliJfooajnpLoiLCB4ID0gIuaMh+WwjuOBleOCjOOBn+eglOeptuiAhSIsIHkgPSAi5oyH5bCO44GX44Gf56CU56m26ICFIikgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpCmBgYAoKIyMjIyMgNC4yLjMuMgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShpZ3JhcGgpCgojIOeglOeptuiAheODquOCueODiApyZXNlYXJjaGVycyA8LSBjKCJBIiwgIkIiLCAiQyIsICJEIiwgIkUiLCAiRiIsICJHIiwgIkgiLCAiSSIsICJKIikKCiMg5YWx6JGX44OH44O844K/CmNvbGxhYm9yYXRpb24gPC0gbWF0cml4KAogIGMoCiAgICBOQSwgMSwgMSwgMCwgMCwgMCwgMCwgMCwgMCwgMSwKICAgIDAsIE5BLCAwLCAxLCAxLCAxLCAwLCAwLCAwLCAwLAogICAgMCwgMCwgTkEsIDAsIDAsIDAsIDEsIDEsIDEsIDAsCiAgICAwLCAwLCAwLCBOQSwgMCwgMCwgMCwgMCwgMCwgMCwKICAgIDAsIDAsIDAsIDAsIE5BLCAwLCAwLCAwLCAwLCAwLAogICAgMCwgMCwgMCwgMCwgMCwgTkEsIDAsIDAsIDAsIDAsCiAgICAwLCAwLCAwLCAwLCAwLCAwLCBOQSwgMCwgMCwgMCwKICAgIDAsIDAsIDAsIDAsIDAsIDAsIDAsIE5BLCAwLCAwLAogICAgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgTkEsIDAsCiAgICAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCBOQQogICAgKSwKICBucm93ID0gMTAsCiAgZGltbmFtZXMgPSBsaXN0KHJlc2VhcmNoZXJzLCByZXNlYXJjaGVycykKICApCgpkaWFnKGNvbGxhYm9yYXRpb24pIDwtIDAKCnBhcihtYXIgPSBjKDAsMCwwLDApKQoKY29sbGFib3JhdGlvbiB8PgogIHQoKSB8PgogIGdyYXBoX2Zyb21fYWRqYWNlbmN5X21hdHJpeChtb2RlID0gImRpcmVjdGVkIikgfD4KICBwbG90KGxheW91dCA9IGxheW91dF9hc190cmVlKQoKcGFyKG1hciA9IGMoNS4xLCA0LjEsIDQuMSwgMi4xKSkKYGBgCgojIyMgNC4yLjQg5qeY44CF44Gq44Os44Kk44Ki44Km44OI44Gr44KI44KL44ON44OD44OI44Ov44O844Kv5o+P55S7CgpjaXJjb+ODrOOCpOOCouOCpuODiOOBjGlncmFwaOOBq+OBr+eEoeOBhOOCiOOBhuOBquOBruOBp01EU+ODrOOCpOOCouOCpuODiOOBp+S7o+eUqOOAggoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShpZ3JhcGgpCgpzZXQuc2VlZCgwKQpHX2VyIDwtIGVyZG9zLnJlbnlpLmdhbWUoMzAsIDAuMikKR193cyA8LSB3YXR0cy5zdHJvZ2F0ei5nYW1lKDEsIDMwLCA1LCAwLjEpCkdfYmEgPC0gYmFyYWJhc2kuZ2FtZSgzMCwgMSkKCkVyZG9zUiA8LSBhc19hZGphY2VuY3lfbWF0cml4KEdfZXIsIHNwYXJzZSA9IEZBTFNFKQpXYXR0c1N0cm9nYXR6IDwtIGFzX2FkamFjZW5jeV9tYXRyaXgoR193cywgc3BhcnNlID0gRkFMU0UpCkJhcmFiYXNpQWxiZXJ0IDwtIGFzX2FkamFjZW5jeV9tYXRyaXgoR19iYSwgc3BhcnNlID0gRkFMU0UpCgpuZXQxIDwtIGdyYXBoX2Zyb21fYWRqYWNlbmN5X21hdHJpeChFcmRvc1IsIG1vZGUgPSAibWF4IikKbmV0MiA8LSBncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgoV2F0dHNTdHJvZ2F0eiwgbW9kZSA9ICJtYXgiKQpuZXQzIDwtIGdyYXBoX2Zyb21fYWRqYWNlbmN5X21hdHJpeChCYXJhYmFzaUFsYmVydCwgbW9kZSA9ICJtYXgiKQoKcGFyKG1mcm93ID0gYygzLDMpLCBtYXIgPSBjKDAsMCwxLDApKQpwbG90KG5ldDEsIGxheW91dCA9IGxheW91dF9pbl9jaXJjbGUsCiAgICAgbWFpbiA9ICJFcmRvcy1SZXluaTogQ2lyY3VsYXIgTGF5b3V0IiwgdmVydGV4LnNpemUgPSA5LCB2ZXJ0ZXgubGFiZWw9TkEsICkKcGxvdChuZXQyLCBsYXlvdXQgPSBsYXlvdXRfaW5fY2lyY2xlLAogICAgIG1haW4gPSAiV2F0dHMtU3Ryb2dhdHo6IENpcmN1bGFyIExheW91dCIsIHZlcnRleC5zaXplID0gOSwgdmVydGV4LmxhYmVsPU5BKQpwbG90KG5ldDMsIGxheW91dCA9IGxheW91dF9pbl9jaXJjbGUsCiAgICAgbWFpbiA9ICJCYXJhYmFzaS1BbGJlcnQ6IENpcmN1bGFyIExheW91dCIsIHZlcnRleC5zaXplID0gOSwgdmVydGV4LmxhYmVsPU5BKQoKcGxvdChuZXQxLCBsYXlvdXQgPSBsYXlvdXRfd2l0aF9tZHMsCiAgICAgbWFpbiA9ICJFcmRvcy1SZXluaTogTURTIExheW91dCIsIHZlcnRleC5zaXplID0gOSwgdmVydGV4LmxhYmVsPU5BKQpwbG90KG5ldDIsIGxheW91dCA9IGxheW91dF93aXRoX21kcywKICAgICBtYWluID0gIldhdHRzLVN0cm9nYXR6OiBNRFMgTGF5b3V0IiwgdmVydGV4LnNpemUgPSA5LCB2ZXJ0ZXgubGFiZWw9TkEpCnBsb3QobmV0MywgbGF5b3V0ID0gbGF5b3V0X3dpdGhfbWRzLAogICAgIG1haW4gPSAiQmFyYWJhc2ktQWxiZXJ0OiBNRFMgTGF5b3V0IiwgdmVydGV4LnNpemUgPSA5LCB2ZXJ0ZXgubGFiZWw9TkEpCgpwbG90KG5ldDEsIGxheW91dCA9IGxheW91dF93aXRoX2trKG5ldDEpLAogICAgIG1haW4gPSAiRXJkb3MtUmV5bmk6IEtLIExheW91dCIsIHZlcnRleC5zaXplID0gOSwgdmVydGV4LmxhYmVsPU5BKQpwbG90KG5ldDIsIGxheW91dCA9IGxheW91dF93aXRoX2trKG5ldDIpLAogICAgIG1haW4gPSAiV2F0dHMtU3Ryb2dhdHo6IEtLIExheW91dCIsIHZlcnRleC5zaXplID0gOSwgdmVydGV4LmxhYmVsPU5BKQpwbG90KG5ldDMsIGxheW91dCA9IGxheW91dF93aXRoX2trKG5ldDMpLAogICAgIG1haW4gPSAiQmFyYWJhc2ktQWxiZXJ0OiBLSyBMYXlvdXQiLCB2ZXJ0ZXguc2l6ZSA9IDksIHZlcnRleC5sYWJlbD1OQSkKCnBhcihtZnJvdyA9IGMoMSwxKSwgbWFyID0gYyg1LjEsIDQuMSwgNC4xLCAyLjEpKQpgYGAKCiMjIyA0LjIuNSDmnInlkJHjg43jg4Pjg4jjg6/jg7zjgq/jga7lj6/oppbljJYKCmRvdOOBqGNpcmNv44GM54Sh44GE44Gu44GndHJlZSwgbWRz44Gn5Luj55SoCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGlncmFwaCkKCiMg44Op44Oz44OA44Og5pyJ5ZCR44Kw44Op44OV44KS55Sf5oiQCnNldC5zZWVkKDApCkdfZGlyX3JhbmRvbSA8LSBzYW1wbGVfZ25tKDMwLCA4MSwgZGlyZWN0ZWQgPSBUUlVFKSAKCiMg6ZqO5bGk5qeL6YCg44KS5oyB44Gk5pyJ5ZCR44Kw44Op44OV44KS55Sf5oiQCnNldC5zZWVkKDApCkdfZGlyX2hpZXJhcmNoeSA8LSBzYW1wbGVfdHJlZSgzMCwgZGlyZWN0ZWQgPSBUUlVFKQoKbXlfcGxvdCA8LSBmdW5jdGlvbihkYXRhLCBsYXlvdXQpewogIHBsb3QoCiAgICBkYXRhLCBsYXlvdXQgPSBsYXlvdXQsCiAgICB2ZXJ0ZXgubGFiZWwgPSBOQSwKICAgIGVkZ2UuYXJyb3cuc2l6ZSA9IDAuNQogICAgKQp9CnBhcihtZnJvdyA9IGMoMywyKSwgbWFyID0gYygwLDAsMCwwKSkKbXlfcGxvdChHX2Rpcl9yYW5kb20sIGxheW91dF9hc190cmVlKQpteV9wbG90KEdfZGlyX2hpZXJhcmNoeSwgbGF5b3V0X2FzX3RyZWUpCm15X3Bsb3QoR19kaXJfcmFuZG9tLCBsYXlvdXRfd2l0aF9tZHMpCm15X3Bsb3QoR19kaXJfaGllcmFyY2h5LCBsYXlvdXRfd2l0aF9tZHMpCm15X3Bsb3QoR19kaXJfcmFuZG9tLCBsYXlvdXRfd2l0aF9raykKbXlfcGxvdChHX2Rpcl9oaWVyYXJjaHksIGxheW91dF93aXRoX2trKQpwYXIobWZyb3cgPSBjKDEsMSksIG1hciA9IGMoNS4xLCA0LjEsIDQuMSwgMi4xKSkKYGBgCgojIyMgNC4zLjEg44Kv44Op44K544K/44O844Oe44OD44OX44Gr44KI44KL44OH44O844K/44Gu5Y+v6KaW5YyWCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHBoZWF0bWFwKQoKZGYgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy93aW5lL3dpbmUuZGF0YSIsCiAgY29sX25hbWVzID0gYygKICAgICLjg5bjg4njgqbjga7lk4HnqK4iLCAi44Ki44Or44Kz44O844Or5bqm5pWwIiwgIuODquODs+OCtOmFuCIsICLjg5/jg43jg6njg6vliIYiLCAKICAgICLjg5/jg43jg6njg6vliIbjga7jgqLjg6vjgqvjg6rluqYiLCAi44Oe44Kw44ON44K344Km44OgIiwgIuWFqOODleOCp+ODjuODvOODq+mhniIsICLjg5Xjg6njg5Djg47jgqTjg4kiLAogICAgIumdnuODleODqeODkOODjuOCpOODieODleOCp+ODjuODvOODq+mhniIsICLjg5fjg63jgqLjg7Pjg4jjgrfjgqLjg4vjg7MiLCAi6Imy44Gu5by344GVIiwgIuiJsuebuCIsCiAgICAiT0QyODAvT0QzMTXlgKQiLCAi44OX44Ot44Oq44OzIgogICksCiAgY29sX3R5cGVzID0gImYiCikgfD4KICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCBcKHgpIHNjYWxlKHgpKSkgI+aomea6luWMlgoKamV0LmNvbG9ycyA8LSBjb2xvclJhbXBQYWxldHRlKGMoIiMwMDAwN0YiLCAiYmx1ZSIsICIjMDA3RkZGIiwgImN5YW4iLCAiIzdGRkY3RiIsICJ5ZWxsb3ciLCAiI0ZGN0YwMCIsICJyZWQiLCAiIzdGMDAwMCIpKQoKcGhlYXRtYXAoCiAgZGYgfD4KICAgIGRwbHlyOjpzZWxlY3QoIeODluODieOCpuOBruWTgeeorikgfD4KICAgIHQoKSwKICBjb2xvciA9IGpldC5jb2xvcnMoNTApLAogIGNsdXN0ZXJpbmdfbWV0aG9kID0gIndhcmQuRDIiCiAgKQpgYGAKCiMjIyA0LjMuMyDmp5jjgIXjgarjgq/jg6njgrnjgr/jg6rjg7PjgrDmiYvms5UKCkJJUkNI44Gg44GR6KaL44Gk44GL44KJ44Gq44GE44Gu44Gn55yB55Wl44CCCgrjgaHjgarjgb/jgatS44Gn44GvW21sYmVuY2hdKGh0dHBzOi8vcWlpdGEuY29tL3B1cnBsZV9qcC9pdGVtcy8wZjJmYzA0ZWZjOTNkYzJhMWY4Zinjg5Hjg4PjgrHjg7zjgrjjgavjg5njg7Pjg4Hjg57jg7zjgq/nlKjjga7mp5jjgIXjgarjg4fjg7zjgr/jgrvjg4Pjg4jjgajkurrlt6Xjg4fjg7zjgr/nlJ/miJDnlKjjga7plqLmlbDjgYznlKjmhI/jgZXjgozjgabjgYTjgb7jgZnjgIIKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlID0gVFJVRX0KbGlicmFyeShjb25mbGljdGVkKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShDbHVzdGVyUikgICAjIE1pbmlCYXRjaCBLTWVhbnMKbGlicmFyeShhcGNsdXN0ZXIpICAjIEFmZmluaXR5IFByb3BhZ2F0aW9uCmxpYnJhcnkobWVhblNoaWZ0UikgIyBNZWFuU2hpZnQKbGlicmFyeShza21lYW5zKSAgICAjIFNwZWN0cmFsIENsdXN0ZXJpbmcKbGlicmFyeShjbHVzdGVyKSAgICAjIEFnZ2xvbWVyYXRpdmUgQ2x1c3RlcmluZwpsaWJyYXJ5KGRic2NhbikgICAgICMgREJTQ0FOLCBIREJTQ0FOLCBPUFRJQ1MsCmxpYnJhcnkobWNsdXN0KSAgICAgIyBHYXVzc2lhbiBNaXh0dXJlCmxpYnJhcnkocGF0Y2h3b3JrKQoKIyDjg4fjg7zjgr/nlKjmhI8KbXlfcmVhZF9jc3YgPC0gZnVuY3Rpb24oZmlsZSl7CiAgcmVhZF9jc3YoZmlsZSwgY29sX25hbWVzID0gYygieCIsICJ5IiwgImNsYXNzIikpIHw+CiAgICBtdXRhdGUoICPmqJnmupbljJbjgZfjgabjgYrjgY8KICAgICAgeCA9ICh4IC0gbWVhbih4KSkvc2QoeCksCiAgICAgIHkgPSAoeSAtIG1lYW4oeCkpL3NkKHgpLCAgCiAgICAgIGNsYXNzID0gZmFjdG9yKGNsYXNzICsgMSkpCn0KCiMg5YaG5b2i44Gu44Kv44Op44K544K/Cm5vaXN5X2NpcmNsZXMgPC0gbXlfcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvbm9pc3lfY2lyY2xlcy5jc3YiCiAgKQoKIyDmnIjlnovjga7jgq/jg6njgrnjgr8Kbm9pc3lfbW9vbnMgPC0gbXlfcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvbm9pc3lfbW9vbnMuY3N2IgogICkgICAgIAoKIyDmraPopo/liIbluIPjgavlvpPjgYbjgq/jg6njgrnjgr8KYmxvYnMgPC0gbXlfcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvYmxvYnMuY3N2IgogICkgICAgCgojIOeVsOaWueaAp+OBruOBguOCi+ODh+ODvOOCvwphbmlzbyA8LSBteV9yZWFkX2NzdigKICAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL21vcmltb3Rvb3NhbXUvZGF0YV92aXN1YWxpemF0aW9uL21haW4vZGF0YS9hbmlzby5jc3YiCiAgKSAgICAgICAKCiMgM+OBpOOBruato+imj+WIhuW4g+OBq+W+k+OBhuODh+ODvOOCvwp2YXJpZWQgPC0gbXlfcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvdmFyaWVkLmNzdiIKICApICAgICAgICAgICAgICAgCgpuIDwtIDUwMApzZXQuc2VlZCgwKQpub19zdHJ1Y3R1cmUgPC0gZGF0YS5mcmFtZSh4ID0gcnVuaWYobiksIHkgPSBydW5pZihuKSwgY2xhc3MgPSBmYWN0b3IoIjAiKSkjIOani+mAoOOBruOBquOBhOODh+ODvOOCvwoKIyDlkITmiYvms5Xjga7jg6njg4Pjg5Hjg7zplqLmlbDjgpLnlKjmhI8KIyBNaW5pQmF0Y2ggS01lYW5zCm15X01pbmlCYXRjaEttZWFucyA8LSBmdW5jdGlvbihkYXRhLCBjbHVzdGVyX251bSkgewogIGRhdGFfY2xlYW5lZCA8LSBkYXRhWywgMToyXQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBmaXQgPC0gTWluaUJhdGNoS21lYW5zKGRhdGFfY2xlYW5lZCwgY2x1c3RlcnMgPSBjbHVzdGVyX251bSkKICBwcmVkIDwtIHByZWRpY3QoZml0LCBkYXRhX2NsZWFuZWQpCiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBBZmZpbml0eSBQcm9wYWdhdGlvbgpteV9hcGNsdXN0ZXIgPC0gZnVuY3Rpb24oZGF0YSwgY2x1c3Rlcl9udW0pIHsKICBkYXRhX2NsZWFuZWQgPC0gZGF0YVssIDE6Ml0KICBzdGFydCA8LSBTeXMudGltZSgpCiAgcHJlZCA8LSBhcGNsdXN0ZXIocyA9IG5lZ0Rpc3RNYXQocj0yKSwgeCA9IGRhdGFfY2xlYW5lZCwgcCA9IC0yMDAsIHEgPSAwLjkpIHw+CiAgICBjdXRyZWUoY2x1c3Rlcl9udW0pIHw+CiAgICBsYWJlbHModHlwZSA9ICJlbnVtIikKICBlbmQgPC0gU3lzLnRpbWUoKQogIGRpZmYgPC0gZW5kIC0gc3RhcnQKICBkYXRhJHByZWQgPC0gYXMuZmFjdG9yKHByZWQpCiAgcmV0dXJuKGxpc3QocmVzID0gZGF0YSwgZGlmZiA9IGRpZmYpKQp9CgojIE1lYW5TaGlmdApteV9tZWFuU2hpZnQgPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGFfY2xlYW5lZCA8LSBhcy5tYXRyaXgoZGF0YVssIDE6Ml0pCiAgc3RhcnQgPC0gU3lzLnRpbWUoKQogIHByZWQgPC0gbWVhblNoaWZ0KGRhdGFfY2xlYW5lZCwgZGF0YV9jbGVhbmVkKSRhc3NpZ25tZW50CiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBTcGVjdHJhbCBDbHVzdGVyaW5nCm15X3NrbWVhbnMgPC0gZnVuY3Rpb24oZGF0YSwgY2x1c3Rlcl9udW0pIHsKICBkYXRhX2NsZWFuZWQgPC0gYmFzZTo6YXMubWF0cml4KGRhdGFbLCAxOjJdKQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBwcmVkIDwtIHNrbWVhbnMoZGF0YV9jbGVhbmVkLCBrID0gY2x1c3Rlcl9udW0pJGNsdXN0ZXIKICBlbmQgPC0gU3lzLnRpbWUoKQogIGRpZmYgPC0gZW5kIC0gc3RhcnQKICBkYXRhJHByZWQgPC0gYXMuZmFjdG9yKHByZWQpCiAgcmV0dXJuKGxpc3QocmVzID0gZGF0YSwgZGlmZiA9IGRpZmYpKQp9CgojIFdhcmQKbXlfaGNsdXN0IDwtIGZ1bmN0aW9uKGRhdGEsIGNsdXN0ZXJfbnVtKSB7CiAgZGF0YV9jbGVhbmVkIDwtIGRhdGFbLCAxOjJdCiAgc3RhcnQgPC0gU3lzLnRpbWUoKQogIHByZWQgPC0gZGF0YV9jbGVhbmVkIHw+CiAgICBzdGF0czo6ZGlzdCgpIHw+CiAgICBoY2x1c3QobWV0aG9kID0gIndhcmQuRDIiKSB8PgogICAgY3V0cmVlKGsgPSBjbHVzdGVyX251bSkKICBlbmQgPC0gU3lzLnRpbWUoKQogIGRpZmYgPC0gZW5kIC0gc3RhcnQKICBkYXRhJHByZWQgPC0gYXMuZmFjdG9yKHByZWQpCiAgcmV0dXJuKGxpc3QocmVzID0gZGF0YSwgZGlmZiA9IGRpZmYpKQp9CgojIEFnZ2xvbWVyYXRpdmUgQ2x1c3RlcmluZwpteV9hZ25lcyA8LSBmdW5jdGlvbihkYXRhLCBjbHVzdGVyX251bSkgewogIGRhdGFfY2xlYW5lZCA8LSBkYXRhWywgMToyXQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBwcmVkIDwtIGFnbmVzKGRhdGFfY2xlYW5lZCkgfD4gY3V0cmVlKGsgPSBjbHVzdGVyX251bSkKICBlbmQgPC0gU3lzLnRpbWUoKQogIGRpZmYgPC0gZW5kIC0gc3RhcnQKICBkYXRhJHByZWQgPC0gYXMuZmFjdG9yKHByZWQpCiAgcmV0dXJuKGxpc3QocmVzID0gZGF0YSwgZGlmZiA9IGRpZmYpKQp9CgojIERCU0NBTgpteV9kYnNjYW4gPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGFfY2xlYW5lZCA8LSBkYXRhWywgMToyXQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBwcmVkIDwtIGRic2Nhbjo6ZGJzY2FuKGRhdGFfY2xlYW5lZCwgZXBzID0gMC4zKSRjbHVzdGVyCiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBIREJTQ0FOCm15X2hkYnNjYW4gPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGFfY2xlYW5lZCA8LSBkYXRhWywgMToyXQogIHN0YXJ0IDwtIFN5cy50aW1lKCkKICBwcmVkIDwtIGRic2Nhbjo6aGRic2NhbihkYXRhX2NsZWFuZWQsIG1pblB0cyA9IDE1KSRjbHVzdGVyCiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBPUFRJQ1MKbXlfb3B0aWNzIDwtIGZ1bmN0aW9uKGRhdGEpIHsKICBkYXRhX2NsZWFuZWQgPC0gZGF0YVssIDE6Ml0KICBzdGFydCA8LSBTeXMudGltZSgpCiAgcHJlZCA8LSBkYnNjYW46Om9wdGljcyhkYXRhX2NsZWFuZWQsIGVwcyA9IDAuMSwgbWluUHRzID0gNykgfD4KICAgIGV4dHJhY3RYaSh4aSA9IDAuMDUpIHw+CiAgICBwbHVjaygiY2x1c3RlciIpCiAgZW5kIDwtIFN5cy50aW1lKCkKICBkaWZmIDwtIGVuZCAtIHN0YXJ0CiAgZGF0YSRwcmVkIDwtIGFzLmZhY3RvcihwcmVkKQogIHJldHVybihsaXN0KHJlcyA9IGRhdGEsIGRpZmYgPSBkaWZmKSkKfQoKIyBHYXVzc2lhbiBNaXh0dXJlCm15X01jbHVzdCA8LSBmdW5jdGlvbihkYXRhKSB7CiAgZGF0YV9jbGVhbmVkIDwtIGRhdGFbLCAxOjJdCiAgc3RhcnQgPC0gU3lzLnRpbWUoKQogIHByZWQgPC0gbWNsdXN0OjpNY2x1c3QoZGF0YV9jbGVhbmVkLCBHID0gMTozKSRjbGFzc2lmaWNhdGlvbgogIGVuZCA8LSBTeXMudGltZSgpCiAgZGlmZiA8LSBlbmQgLSBzdGFydAogIGRhdGEkcHJlZCA8LSBhcy5mYWN0b3IocHJlZCkKICByZXR1cm4obGlzdChyZXMgPSBkYXRhLCBkaWZmID0gZGlmZikpCn0KCiMjIyDjgq/jg6njgrnjgr/jg6rjg7PjgrDlrp/ooYwKZGF0cyA8LSBsaXN0KG5vaXN5X2NpcmNsZXMsIG5vaXN5X21vb25zLCB2YXJpZWQsIGFuaXNvLCBibG9icywgbm9fc3RydWN0dXJlKQpjbnVtcyA8LSBsaXN0KDIsMiwzLDMsMywzKQoKc2V0LnNlZWQoMCkKcmVzX2NsdXN0ZXJpbmcgPC0gbGlzdCgKICByZXNfTWluaUJhdGNoS21lYW5zID0gbWFwMihkYXRzLCBjbnVtcywgXChkYXRzLCBjbnVtcykgbXlfTWluaUJhdGNoS21lYW5zKGRhdHMsIGNudW1zKSksCiAgcmVzX2FwY2x1c3RlciAgICAgICA9IG1hcDIoZGF0cywgY251bXMsIFwoZGF0cywgY251bXMpIG15X2FwY2x1c3RlcihkYXRzLCBjbnVtcykpLAogIHJlc19tZWFuU2hpZnQgICAgICAgPSBwdXJycjo6bWFwKGRhdHMsIFwoZGF0cykgbXlfbWVhblNoaWZ0KGRhdHMpKSwKICByZXNfc2ttZWFucyAgICAgICAgID0gbWFwMihkYXRzLCBjbnVtcywgXChkYXRzLCBjbnVtcykgbXlfc2ttZWFucyhkYXRzLCBjbnVtcykpLAogIHJlc19oY2x1c3QgICAgICAgICAgPSBtYXAyKGRhdHMsIGNudW1zLCBcKGRhdHMsIGNudW1zKSBteV9oY2x1c3QoZGF0cywgY251bXMpKSwKICByZXNfYWduZXMgICAgICAgICAgID0gbWFwMihkYXRzLCBjbnVtcywgXChkYXRzLCBjbnVtcykgbXlfYWduZXMoZGF0cywgY251bXMpKSwKICByZXNfZGJzY2FuICAgICAgICAgID0gcHVycnI6Om1hcChkYXRzLCBcKGRhdHMpIG15X2Ric2NhbihkYXRzKSksCiAgcmVzX2hkYnNjYW4gICAgICAgICA9IHB1cnJyOjptYXAoZGF0cywgXChkYXRzKSBteV9oZGJzY2FuKGRhdHMpKSwKICByZXNfb3B0aWNzICAgICAgICAgID0gcHVycnI6Om1hcChkYXRzLCBcKGRhdHMpIG15X29wdGljcyhkYXRzKSksCiAgcmVzX01jbHVzdCAgICAgICAgICA9IHB1cnJyOjptYXAoZGF0cywgXChkYXRzKSBteV9NY2x1c3QoZGF0cykpCikgCgojIOaPj+eUu+eUqOODqeODg+ODkeODvOmWouaVsOOCkueUqOaEjwpteV9wbG90IDwtIGZ1bmN0aW9uKHJlc3VsdCkgewogIHJlc3VsdCRyZXMgfD4KICAgIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5LCBjb2xvciA9IHByZWQpKSArCiAgICBnZW9tX3BvaW50KHNpemUgPSAxKSArIAogICAgbGFicyhjYXB0aW9uID0gcGFzdGUwKHJvdW5kKHJlc3VsdCRkaWZmLDMpLCJzIikpICsKICAgIHRoZW1lKAogICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCAgIyB0aWNr44Gu57ea44KS5raI44GZCiAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgICAjIHRpY2vjga7mlbDlrZfjgpLmtojjgZkKICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwgICMg6Lu444Gu44Op44OZ44Or44KS5raI44GZCiAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgICAjIOi7uOOBrue3muOCkua2iOOBmQogICAgICBsZWdlbmQucG9zaXRpb249Im5vbmUiLAogICAgICBhc3BlY3QucmF0aW8gPSAxCiAgICAgICkKfQoKbnVtcyA8LSBleHBhbmRfZ3JpZChkID0gMTo2LCBtID0gMToxMCkKcmVzX3Bsb3QgPC0gbWFwMihudW1zJG0sIG51bXMkZCwgXCgueCwgLnkpIG15X3Bsb3QocmVzX2NsdXN0ZXJpbmdbWy54XV1bWy55XV0pKQoKIyDjgZPjgZPjgYzjg4DjgrXjgYTigKbigKbjgoLjgYbjgaHjgofjgaPjgajjganjgYbjgavjgYvjgarjgonjgarjgYTjgYsKcmVzX3Bsb3RbWzFdXSArIHJlc19wbG90W1syXV0gK3Jlc19wbG90W1szXV0gK3Jlc19wbG90W1s0XV0gK3Jlc19wbG90W1s1XV0gK3Jlc19wbG90W1s2XV0gKwogIHJlc19wbG90W1s3XV0gKyByZXNfcGxvdFtbOF1dICtyZXNfcGxvdFtbOV1dICtyZXNfcGxvdFtbMTBdXSArcmVzX3Bsb3RbWzExXV0gK3Jlc19wbG90W1sxMl1dICsKICByZXNfcGxvdFtbMTNdXSArIHJlc19wbG90W1sxNF1dICtyZXNfcGxvdFtbMTVdXSArcmVzX3Bsb3RbWzE2XV0gK3Jlc19wbG90W1sxN11dICtyZXNfcGxvdFtbMThdXSArCiAgcmVzX3Bsb3RbWzE5XV0gKyByZXNfcGxvdFtbMjBdXSArcmVzX3Bsb3RbWzIxXV0gK3Jlc19wbG90W1syMl1dICtyZXNfcGxvdFtbMjNdXSArcmVzX3Bsb3RbWzI0XV0gKwogIHJlc19wbG90W1syNV1dICsgcmVzX3Bsb3RbWzI2XV0gK3Jlc19wbG90W1syN11dICtyZXNfcGxvdFtbMjhdXSArcmVzX3Bsb3RbWzI5XV0gK3Jlc19wbG90W1szMF1dICsKICByZXNfcGxvdFtbMzFdXSArIHJlc19wbG90W1szMl1dICtyZXNfcGxvdFtbMzNdXSArcmVzX3Bsb3RbWzM0XV0gK3Jlc19wbG90W1szNV1dICtyZXNfcGxvdFtbMzZdXSArCiAgcmVzX3Bsb3RbWzM3XV0gKyByZXNfcGxvdFtbMzhdXSArcmVzX3Bsb3RbWzM5XV0gK3Jlc19wbG90W1s0MF1dICtyZXNfcGxvdFtbNDFdXSArcmVzX3Bsb3RbWzQyXV0gKwogIHJlc19wbG90W1s0M11dICsgcmVzX3Bsb3RbWzQ0XV0gK3Jlc19wbG90W1s0NV1dICtyZXNfcGxvdFtbNDZdXSArcmVzX3Bsb3RbWzQ3XV0gK3Jlc19wbG90W1s0OF1dICsKICByZXNfcGxvdFtbNDldXSArIHJlc19wbG90W1s1MF1dICtyZXNfcGxvdFtbNTFdXSArcmVzX3Bsb3RbWzUyXV0gK3Jlc19wbG90W1s1M11dICtyZXNfcGxvdFtbNTRdXSArCiAgcmVzX3Bsb3RbWzU1XV0gKyByZXNfcGxvdFtbNTZdXSArcmVzX3Bsb3RbWzU3XV0gK3Jlc19wbG90W1s1OF1dICtyZXNfcGxvdFtbNTldXSArcmVzX3Bsb3RbWzYwXV0gKwogIHBsb3RfbGF5b3V0KG5jb2wgPSAxMCkKYGBgCgojIyMgNC4zLjQg5aSa5aSJ5pWw44KS44Oa44Ki44OX44Ot44OD44OI44Gn6KaL44KLCgpgYGB7ciBmaWcuaGVpZ2h0PTcuNSwgZmlnLndpZHRoPTcuNSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGUgPSBUUlVFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KEdHYWxseSkKCmRmIDwtIHJlYWRfY3N2KAogICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbW9yaW1vdG9vc2FtdS9kYXRhX3Zpc3VhbGl6YXRpb24vbWFpbi9kYXRhL2RmXzQzNC5jc3YiCiAgKQoKZGYgfD4KICBnZ3BhaXJzKCkKYGBgCgojIyMgNC4zLjUg5Li75oiQ5YiG5YiG5p6Q44Gu44Kk44Oh44O844K4CgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNrbWVhbnMpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KHBhdGNod29yaykKCmRmIDwtIHJlYWRfY3N2KAogICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbW9yaW1vdG9vc2FtdS9kYXRhX3Zpc3VhbGl6YXRpb24vbWFpbi9kYXRhL2RmXzQzNC5jc3YiCiAgKQoKIyDkuLvmiJDliIbliIbmnpDvvIjmqJnmupbljJbvvIkKcGNhX3Jlc3VsdCA8LSBwcmNvbXAoZGYpIywgc2NhbGUuID0gVFJVRSkKcGNhX2ltcG9ydGFuY2UgPC0gYXMuZGF0YS5mcmFtZShzdW1tYXJ5KHBjYV9yZXN1bHQpJGltcG9ydGFuY2VbMixdKSB8PgogIHJvd25hbWVzX3RvX2NvbHVtbigpCm5hbWVzKHBjYV9pbXBvcnRhbmNlKSA8LSBjKCJwYyIsICJ2YWx1ZSIpCgojIFNwZWN0cmFsIENsdXN0ZXJpbmcKY2x1c3RlcnNfcGNhIDwtIHNrbWVhbnMocGNhX3Jlc3VsdCR4WywgMToyXSwgMykKCnAxIDwtIGRhdGEuZnJhbWUoCiAgcGMxID0gcGNhX3Jlc3VsdCR4WywgMV0sCiAgcGMyID0gcGNhX3Jlc3VsdCR4WywgMl0sCiAgY2xhc3MgPSBmYWN0b3IoY2x1c3RlcnNfcGNhJGNsdXN0ZXIpCiAgICApIHw+CiAgZ2dwbG90KGFlcyh4ID0gcGMxLCB5ID0gcGMyLCBjb2xvciA9IGNsYXNzKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZSA9ICLkuozjgaTjga7kuLvmiJDliIbjgafopovjgosiKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLCBhc3BlY3QucmF0aW8gPSAxKQoKcDIgPC0gcGNhX2ltcG9ydGFuY2UgfD4KICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKHBjLCBkZXNjKHZhbHVlKSksIHkgPSB2YWx1ZSkpICsKICBnZW9tX2NvbCgpKwogIGxhYnModGl0bGUgPSAi5ZCE5Li75oiQ5YiG44Gu44OH44O844K/5oKm5piO5YqbIiwgeCA9ICIiLCB5ID0gIiIpKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEpCgpwMSArIHAyCmBgYAoKIyMjIDQuMy42IOeUu+WDj+ODh+ODvOOCv+OBruasoeWFg+WJiua4mwoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGUgPSBUUlVFfQpsaWJyYXJ5KGNvbmZsaWN0ZWQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KE1BU1MpICMgTURTKHNhbW1vbikKbGlicmFyeShSdHNuZSkgIyB0LVNORQpsaWJyYXJ5KHVtYXApICMgVU1BUAoKZGYgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tb3JpbW90b29zYW11L2RhdGFfdmlzdWFsaXphdGlvbi9tYWluL2RhdGEvZGlnaXRzLmNzdiIKICApCgpkZl9jbGVhbmVkIDwtIGRmIHw+CiAgZHBseXI6OnNlbGVjdCghdGFyZ2V0KQoKIyDlkITmiYvms5Xjgacy5qyh5YWD44Gr5Zyn57iuCiMg5Li75oiQ5YiG5YiG5p6QClhfcGNhIDwtIHByY29tcChkZl9jbGVhbmVkKSR4WywgMToyXQoKIyB0LVNORQpzZXQuc2VlZCg0MikKWF90c25lIDwtIFJ0c25lKGRmX2NsZWFuZWQsIG51bV90aHJlYWRzID0gMikkWQoKIyBNRFMKWF9tZHMgPC0gZGZfY2xlYW5lZCB8PgogIGRpc3QoKSB8PgogIHNhbW1vbih0cmFjZSA9IEZBTFNFKSB8PgogIHBsdWNrKCJwb2ludHMiKQoKIyBVTUFQCnNldC5zZWVkKDQyKQpYX3VtYXAgPC0gdW1hcChkZl9jbGVhbmVkKSRsYXlvdXQKCiMgSy1tZWFucwpzZXQuc2VlZCg0MikKY2x1c3RlcnNfcGNhIDwtIGttZWFucyhYX3BjYSwgMTApJGNsdXN0ZXIgICMgUENBCnNldC5zZWVkKDQyKQpjbHVzdGVyc19tZHMgPC0ga21lYW5zKFhfbWRzLCAxMCkkY2x1c3RlciAjTURTCnNldC5zZWVkKDQyKQpjbHVzdGVyc190c25lIDwtIGttZWFucyhYX3RzbmUsIDEwKSRjbHVzdGVyICMgdC1TTkUKc2V0LnNlZWQoNDIpCmNsdXN0ZXJzX3VtYXAgPC0ga21lYW5zKFhfdW1hcCwgMTApJGNsdXN0ZXIgICMgVU1BUAoKIyDntZDmnpzjgpLjg4fjg7zjgr/jg5Xjg6zjg7zjg6Djgavjgb7jgajjgoHjgosKZGZfYmFzZSA8LSBkYXRhLmZyYW1lKAogICAgeCA9IGMoWF9wY2FbLCAxXSwgWF9tZHNbLCAxXSwgWF90c25lWywgMV0sIFhfdW1hcFssIDFdKSwKICAgIHkgPSBjKFhfcGNhWywgMl0sIFhfbWRzWywgMl0sIFhfdHNuZVssIDJdLCBYX3VtYXBbLCAyXSkKICAgICkKCm4gPC0gMTc5NwoKZGltcmVkIDwtIGJpbmRfcm93cygKICBkZl9iYXNlIHw+CiAgICBtdXRhdGUoCiAgICAgIGxhYmVsID0gcmVwKGRmJHRhcmdldCwgNCksCiAgICAgIG1ldGhvZCA9IGMocmVwKCJwY2FfbGFiZWwiLCBuKSwgcmVwKCJtZHNfbGFiZWwiLCBuKSxyZXAoInRzbmVfbGFiZWwiLCBuKSxyZXAoInVtYXBfbGFiZWwiLCBuKSkKICAgICAgKSwKICBkZl9iYXNlIHw+CiAgICBtdXRhdGUoCiAgICAgIGxhYmVsID0gYyhjbHVzdGVyc19wY2EsIGNsdXN0ZXJzX21kcywgY2x1c3RlcnNfdHNuZSwgY2x1c3RlcnNfdW1hcCksCiAgICAgIG1ldGhvZCA9IGMocmVwKCJwY2Ffa21lYW5zIiwgbiksIHJlcCgibWRzX2ttZWFucyIsIG4pLHJlcCgidHNuZV9rbWVhbnMiLCBuKSxyZXAoInVtYXBfa21lYW5zIiwgbikpCiAgICAgICkKKSB8PgogIG11dGF0ZSgKICAgIG1ldGhvZCA9IGZhY3RvcigKICAgICAgbWV0aG9kLAogICAgICBsZXZlbHMgPSBjKCJwY2Ffa21lYW5zIiwgInBjYV9sYWJlbCIsICJtZHNfa21lYW5zIiwgIm1kc19sYWJlbCIsCiAgICAgICAgICAgICAgICAgInRzbmVfa21lYW5zIiwgICJ0c25lX2xhYmVsIiwgInVtYXBfa21lYW5zIiwgInVtYXBfbGFiZWwiKSksCiAgICBsYWJlbCA9IGZhY3RvcihsYWJlbCkKICAgICkKCiMg44Kv44Op44K544K/44Oq44Oz44Kw57WQ5p6c44Go5q2j6Kej44Op44OZ44Or44Gu5o+P55S7CmRpbXJlZCB8PgogIGdncGxvdChhZXMoeCA9IHgsIHkgPSB5LCBjb2xvciA9IGxhYmVsKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBsYWJzKHRpdGxlPSLmp5jjgIXjgarmrKHlhYPlnKfnuK7mlrnms5UiKSArCiAgdGhlbWUoCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCAgIyB0aWNr44Gu57ea44KS5raI44GZCiAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksICAgIyB0aWNr44Gu5pWw5a2X44KS5raI44GZCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLCAgIyDou7jjga7jg6njg5njg6vjgpLmtojjgZkKICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgICAjIOi7uOOBrue3muOCkua2iOOBmQogICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgIGFzcGVjdC5yYXRpbyA9IDEKICApICsKICBmYWNldF93cmFwKHZhcnMobWV0aG9kKSwgbnJvdyA9IDIsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgrnrKw056ug44Gv44GT44GT44G+44Gn44CCCg==